To implement JWT (JSON Web Token) refresh token logic in a Web API, the general idea is to issue two tokens when a user successfully logs in:
- Access Token (short-lived): This token is used for authenticating requests and is typically valid for a short duration (e.g., 15 minutes).
- Refresh Token (long-lived): This token is used to get a new access token when the old one expires. It typically has a longer expiry time (e.g., 7 days or more).
Steps to Implement JWT Refresh Token in a Web API
1. Login Endpoint (Generate Access and Refresh Tokens)
When the user successfully logs in, generate both an access token and a refresh token. The access token will be used for authentication, and the refresh token will be stored securely (typically in an HttpOnly cookie or in the database).
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
// Validate user credentials
var user = AuthenticateUser(request.Username, request.Password);
if (user == null) return Unauthorized();
// Generate JWT Access Token
var accessToken = GenerateAccessToken(user);
// Generate Refresh Token
var refreshToken = GenerateRefreshToken();
// Save the refresh token in the database or cache associated with the user
return Ok(new
{
AccessToken = accessToken,
RefreshToken = refreshToken
});
}
private string GenerateAccessToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString())
// Add other claims as necessary
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
_configuration["Jwt:SecretKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(15), // short-lived access token
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
}
return Convert.ToBase64String(randomNumber);
}
}
2. Refresh Token Endpoint
When the access token expires, the client sends the refresh token to this endpoint to get a new access token. The refresh token is validated, and if valid, a new access token is issued.
[HttpPost("refresh-token")]
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
{
// Validate the refresh token
var user = ValidateRefreshToken(request.RefreshToken);
if (user == null) return Unauthorized();
// Generate new access token
var accessToken = GenerateAccessToken(user);
// Optionally, generate a new refresh token and save it (if you are rotating
// refresh tokens)
var refreshToken = GenerateRefreshToken();
SaveRefreshToken(user, refreshToken);
return Ok(new
{
AccessToken = accessToken,
RefreshToken = refreshToken
});
}
private User ValidateRefreshToken(string refreshToken)
{
// Validate the refresh token. Check if it's valid and matches what's stored
// in the database.
// This could involve checking a database, cache, or other storage for a
// matching refresh token.
// Implement this logic based on your storage method
return GetUserByRefreshToken(refreshToken);
}
3. Refresh Token Storage
Refresh tokens should be securely stored. Options include:
- HttpOnly Cookies: Secure and less vulnerable to XSS attacks.
- Database: Store refresh tokens in a table or cache, mapping them to user accounts and ensuring the refresh token is revoked after use.
If you're storing the refresh token in a cookie, set the HttpOnly
flag and Secure
flag to ensure the token is sent only over HTTPS and cannot be accessed via JavaScript.
Example for setting refresh token in HttpOnly cookie:
Response.Cookies.Append("refresh_token", refreshToken, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.Now.AddDays(7) // Set expiry to match your policy
});
4. Token Expiry and Invalidating Refresh Tokens
Make sure that:
- Access tokens expire quickly (e.g., in 15 minutes).
- Refresh tokens can either have a long expiration time (e.g., 7 days) or a single-use (rotating refresh tokens) mechanism to increase security.
- When a refresh token is used, it’s either revoked or replaced with a new refresh token to avoid token reuse.
5. Middleware for Token Validation
The access token is included in request headers (typically in the Authorization
header) as a bearer token. This token is validated with each request to ensure it is still valid.
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public JwtMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?
.Split(" ").Last();
if (token != null)
{
AttachUserToContext(context, token);
}
await _next(context);
}
private void AttachUserToContext(HttpContext context, string token)
{
try
{
var claimsPrincipal = ValidateToken(token);
context.User = claimsPrincipal;
}
catch
{
context.User = null;
}
}
private ClaimsPrincipal ValidateToken(string token)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
_configuration["Jwt:SecretKey"]));
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
IssuerSigningKey = key,
ClockSkew = TimeSpan.Zero,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"]
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
}
Key Considerations:
- Secure Storage: Always ensure that refresh tokens are stored securely, either in a database or HttpOnly cookies.
- Token Rotation: Optionally, rotate refresh tokens to increase security (i.e., issue a new refresh token each time a new access token is issued).
- Revocation: Implement a strategy for revoking refresh tokens if necessary (e.g., after logout or password change).
This setup should give you a basic implementation of JWT with access token and refresh token in your Web API.