Skip to content

Instantly share code, notes, and snippets.

@manoj-choudhari-git
Created December 5, 2020 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manoj-choudhari-git/a704d77c029c2798adb6d3fcdbe2f0b5 to your computer and use it in GitHub Desktop.
Save manoj-choudhari-git/a704d77c029c2798adb6d3fcdbe2f0b5 to your computer and use it in GitHub Desktop.
.NET 5 Web API controller to generate access tokens and refresh tokens
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly JwtBearerTokenSettings jwtBearerTokenSettings;
private readonly UserManager<ApplicationUser> userManager;
private readonly ApplicationDbContext dbContext;
public AuthController(IOptions<JwtBearerTokenSettings> jwtTokenOptions, UserManager<ApplicationUser> userManager, ApplicationDbContext dbContext)
{
this.jwtBearerTokenSettings = jwtTokenOptions.Value;
this.userManager = userManager;
this.dbContext = dbContext;
}
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register([FromBody] UserDetails userDetails)
{
if (!ModelState.IsValid || userDetails == null)
{
return new BadRequestObjectResult(new { Message = "User Registration Failed" });
}
var identityUser = new ApplicationUser() { UserName = userDetails.UserName, Email = userDetails.Email };
var result = await userManager.CreateAsync(identityUser, userDetails.Password);
if (!result.Succeeded)
{
var dictionary = new ModelStateDictionary();
foreach (IdentityError error in result.Errors)
{
dictionary.AddModelError(error.Code, error.Description);
}
return new BadRequestObjectResult(new { Message = "User Registration Failed", Errors = dictionary });
}
return Ok(new { Message = "User Reigstration Successful" });
}
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login([FromBody] LoginCredentials credentials)
{
ApplicationUser identityUser;
if (!ModelState.IsValid
|| credentials == null
|| (identityUser = await ValidateUser(credentials)) == null)
{
return new BadRequestObjectResult(new { Message = "Login failed" });
}
var token = GenerateTokens(identityUser);
return Ok(new { Token = token, Message = "Success" });
}
[AllowAnonymous]
[HttpPost]
[Route("RefreshToken")]
public IActionResult RefreshToken()
{
var token = HttpContext.Request.Cookies["refreshToken"];
var identityUser = dbContext.Users.Include(x => x.RefreshTokens)
.FirstOrDefault(x => x.RefreshTokens.Any(y => y.Token == token && y.UserId == x.Id));
// Get existing refresh token if it is valid and revoke it
var existingRefreshToken = GetValidRefreshToken(token, identityUser);
if(existingRefreshToken == null)
{
return new BadRequestObjectResult(new { Message = "Failed" });
}
existingRefreshToken.RevokedByIp = HttpContext.Connection.RemoteIpAddress.ToString();
existingRefreshToken.RevokedOn = DateTime.UtcNow;
// Generate new tokens
var newToken = GenerateTokens(identityUser);
return Ok(new { Token = newToken, Message = "Success" });
}
[HttpPost]
[Route("RevokeToken")]
public IActionResult RevokeToken(string token)
{
// If user found, then revoke
if (RevokeRefreshToken(token))
{
return Ok(new { Message = "Success" });
}
// Otherwise, return error
return new BadRequestObjectResult(new { Message = "Failed" });
}
[HttpPost]
[Route("Logout")]
public async Task<IActionResult> Logout()
{
// Revoke Refresh Token
RevokeRefreshToken();
return Ok(new { Token = "", Message = "Logged Out" });
}
private RefreshToken GetValidRefreshToken(string token, ApplicationUser identityUser)
{
if (identityUser == null)
{
return null;
}
var existingToken = identityUser.RefreshTokens.FirstOrDefault(x => x.Token == token);
return IsRefreshTokenValid(existingToken) ? existingToken : null;
}
private bool RevokeRefreshToken(string token = null)
{
token = token == null ? HttpContext.Request.Cookies["refreshToken"] : token;
var identityUser = dbContext.Users.Include(x => x.RefreshTokens)
.FirstOrDefault(x => x.RefreshTokens.Any(y => y.Token == token && y.UserId == x.Id));
if (identityUser == null)
{
return false;
}
// Revoke Refresh token
var existingToken = identityUser.RefreshTokens.FirstOrDefault(x => x.Token == token);
existingToken.RevokedByIp = HttpContext.Connection.RemoteIpAddress.ToString();
existingToken.RevokedOn = DateTime.UtcNow;
dbContext.Update(identityUser);
dbContext.SaveChanges();
return true;
}
private async Task<ApplicationUser> ValidateUser(LoginCredentials credentials)
{
var identityUser = await userManager.FindByNameAsync(credentials.Username);
if (identityUser != null)
{
var result = userManager.PasswordHasher.VerifyHashedPassword(identityUser, identityUser.PasswordHash, credentials.Password);
return result == PasswordVerificationResult.Failed ? null : identityUser;
}
return null;
}
private string GenerateTokens(ApplicationUser identityUser)
{
// Generate access token
string accessToken = GenerateAccessToken(identityUser);
// Generate refresh token and set it to cookie
var ipAddress = HttpContext.Connection.RemoteIpAddress.ToString();
var refreshToken = GenerateRefreshToken(ipAddress, identityUser.Id);
// Set Refresh Token Cookie
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7)
};
HttpContext.Response.Cookies.Append("refreshToken", refreshToken.Token, cookieOptions);
// Save refresh token to database
if (identityUser.RefreshTokens == null)
{
identityUser.RefreshTokens = new List<RefreshToken>();
}
identityUser.RefreshTokens.Add(refreshToken);
dbContext.Update(identityUser);
dbContext.SaveChanges();
return accessToken;
}
private string GenerateAccessToken(ApplicationUser identityUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(jwtBearerTokenSettings.SecretKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, identityUser.UserName.ToString()),
new Claim(ClaimTypes.Email, identityUser.Email)
}),
Expires = DateTime.Now.AddSeconds(jwtBearerTokenSettings.ExpiryTimeInSeconds),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Audience = jwtBearerTokenSettings.Audience,
Issuer = jwtBearerTokenSettings.Issuer
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private RefreshToken GenerateRefreshToken(string ipAddress, string userId)
{
using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider())
{
var randomBytes = new byte[64];
rngCryptoServiceProvider.GetBytes(randomBytes);
return new RefreshToken
{
Token = Convert.ToBase64String(randomBytes),
ExpiryOn = DateTime.UtcNow.AddDays(jwtBearerTokenSettings.RefreshTokenExpiryInDays),
CreatedOn = DateTime.UtcNow,
CreatedByIp = ipAddress,
UserId = userId
};
}
}
private bool IsRefreshTokenValid(RefreshToken existingToken)
{
// Is token already revoked, then return false
if (existingToken.RevokedByIp != null && existingToken.RevokedOn != DateTime.MinValue)
{
return false;
}
// Token already expired, then return false
if (existingToken.ExpiryOn <= DateTime.UtcNow)
{
return false;
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment