Isaac.

security

JWT Token Refresh Patterns

Implement secure token refresh mechanisms for JWT authentication.

By Emem IsaacOctober 12, 20233 min read
#jwt#authentication#refresh tokens#security
Share:

A Simple Analogy

JWT refresh tokens are like temporary passes that can get new passes. Your main pass expires quickly, but you can trade your refresh token for a new one.


Why Token Refresh?

  • Short lived: Access tokens expire soon
  • Revocation: Invalidate tokens quickly
  • Security: Minimize exposure window
  • Flexibility: Update permissions
  • Logout: Refresh tokens can be blacklisted

Token Structure

public class TokenResponse
{
    public string AccessToken { get; set; }      // Short-lived (15 min)
    public string RefreshToken { get; set; }     // Long-lived (7 days)
    public int ExpiresIn { get; set; }           // 900 seconds
}

Issuing Tokens

public class AuthService
{
    private readonly IConfiguration _config;
    private readonly IHttpContextAccessor _httpContext;
    
    public AuthService(IConfiguration config, IHttpContextAccessor httpContext)
    {
        _config = config;
        _httpContext = httpContext;
    }
    
    public TokenResponse GenerateTokens(User user)
    {
        var accessToken = GenerateAccessToken(user);
        var refreshToken = GenerateRefreshToken();
        
        // Store refresh token in database
        user.RefreshToken = refreshToken;
        user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
        
        return new TokenResponse
        {
            AccessToken = accessToken,
            RefreshToken = refreshToken,
            ExpiresIn = 900
        };
    }
    
    private string GenerateAccessToken(User user)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_config["Jwt:Key"]);
        
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Email, user.Email),
                new Claim("role", user.Role)
            }),
            Expires = DateTime.UtcNow.AddMinutes(15),
            SigningCredentials = new SigningCredentials(
                new SymmetricSecurityKey(key), 
                SecurityAlgorithms.HmacSha256Signature)
        };
        
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
    
    private string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];
        using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }
}

Refresh Endpoint

[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly AuthService _authService;
    private readonly IRepository<User> _userRepository;
    
    [HttpPost("refresh")]
    public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
    {
        try
        {
            // Find user with refresh token
            var user = await _userRepository.FirstOrDefaultAsync(
                u => u.RefreshToken == request.RefreshToken);
            
            if (user == null)
                return Unauthorized("Invalid refresh token");
            
            // Check expiry
            if (user.RefreshTokenExpiry < DateTime.UtcNow)
                return Unauthorized("Refresh token expired");
            
            // Generate new tokens
            var tokens = _authService.GenerateTokens(user);
            await _userRepository.UpdateAsync(user);
            
            return Ok(tokens);
        }
        catch (Exception ex)
        {
            return Unauthorized(new { message = ex.Message });
        }
    }
}

public class RefreshTokenRequest
{
    public string RefreshToken { get; set; }
}

Client Implementation

// Store tokens
localStorage.setItem('accessToken', response.data.accessToken);
localStorage.setItem('refreshToken', response.data.refreshToken);

// API interceptor
async function apiCall(url, options = {}) {
  let accessToken = localStorage.getItem('accessToken');
  
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  // Token expired?
  if (response.status === 401) {
    const refreshToken = localStorage.getItem('refreshToken');
    
    const newTokens = await fetch('/api/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken })
    }).then(r => r.json());
    
    // Update tokens
    localStorage.setItem('accessToken', newTokens.accessToken);
    localStorage.setItem('refreshToken', newTokens.refreshToken);
    
    // Retry request
    return apiCall(url, options);
  }
  
  return response.json();
}

Token Rotation

// Rotate tokens on every refresh
public TokenResponse RefreshWithRotation(string oldRefreshToken)
{
    var user = ValidateRefreshToken(oldRefreshToken);
    
    // Generate new tokens
    var accessToken = GenerateAccessToken(user);
    var newRefreshToken = GenerateRefreshToken();
    
    // Invalidate old refresh token
    user.RefreshToken = newRefreshToken;
    user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
    
    return new TokenResponse
    {
        AccessToken = accessToken,
        RefreshToken = newRefreshToken,
        ExpiresIn = 900
    };
}

Best Practices

  1. Short access tokens: 15-30 minutes
  2. Longer refresh tokens: 7-30 days
  3. Secure storage: HttpOnly cookies
  4. Rotation: Generate new refresh tokens
  5. Revocation: Blacklist tokens on logout

Related Concepts

  • OAuth 2.0 patterns
  • Token blacklisting
  • Cookie-based authentication
  • Multi-factor authentication

Summary

Token refresh patterns balance security with user experience. Use short-lived access tokens with longer-lived refresh tokens, and rotate tokens on refresh for enhanced security.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours