Refresh Tokens & Secure Session Management in ASP.NET Core

Samim.Hossain
Samim Hossain
Published on Mar, 15 2026 2 min read 0 comments
image

Authentication doesn’t end after issuing a JWT. Real-world applications must handle token expiration, session continuity, and security threats like token theft.
In this article, we’ll explore Refresh Tokens and how to build secure session management in ASP.NET Core.

Why Refresh Tokens Are Important

JWT access tokens are:

  • Stateless
  • Short-lived
  • Not revocable easily

If access tokens never expire → huge security risk
If they expire too quickly → bad user experience

👉 Refresh tokens solve this problem

Token Strategy (Best Practice)

| Token Type    | Lifetime     | Purpose              |
| ------------- | ------------ | -------------------- |
| Access Token  | 5–15 minutes | API authorization    |
| Refresh Token | Days / Weeks | Get new access token |

How Refresh Tokens Work (Flow)

  1. User logs in
  2. Server issues:
    • Access Token (JWT)
    • Refresh Token (secure random string)
  3. Client stores:
    • Access Token → memory
    • Refresh Token → HTTP-only cookie
  4. Access token expires
  5. Client calls /refresh
  6. Server validates refresh token
  7. New access token is issued

Designing a Secure Refresh Token System

🔐 Key Security Principles

  • Refresh tokens must be:
    • Random
    • Stored server-side
    • Revocable
  • Use token rotation
  • Store refresh tokens hashed in DB
  • Invalidate tokens on:
    • Logout
    • Password change
    • Suspicious activity

Database Model Example

public class RefreshToken
{
    public int Id { get; set; }
    public string TokenHash { get; set; }
    public DateTime ExpiresAt { get; set; }
    public bool IsRevoked { get; set; }
    public string UserId { get; set; }
}

Generating a Secure Refresh Token

public string GenerateRefreshToken()
{
    var randomBytes = RandomNumberGenerator.GetBytes(64);
    return Convert.ToBase64String(randomBytes);
}

Hash Before Storing

public string HashToken(string token)
{
    using var sha256 = SHA256.Create();
    return Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(token)));
}

Login Endpoint Example

[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto dto)
{
    // Validate user credentials

    var accessToken = GenerateJwt(user);
    var refreshToken = GenerateRefreshToken();

    SaveRefreshToken(user.Id, HashToken(refreshToken));

    Response.Cookies.Append("refreshToken", refreshToken, new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict,
        Expires = DateTime.UtcNow.AddDays(7)
    });

    return Ok(new { accessToken });
}

Refresh Token Endpoint

[HttpPost("refresh")]
public IActionResult Refresh()
{
    var refreshToken = Request.Cookies["refreshToken"];
    if (string.IsNullOrEmpty(refreshToken))
        return Unauthorized();

    var tokenHash = HashToken(refreshToken);
    var storedToken = _db.RefreshTokens.FirstOrDefault(t => t.TokenHash == tokenHash);

    if (storedToken == null || storedToken.IsRevoked || storedToken.ExpiresAt < DateTime.UtcNow)
        return Unauthorized();

    // Rotate token
    storedToken.IsRevoked = true;

    var newRefreshToken = GenerateRefreshToken();
    SaveRefreshToken(storedToken.UserId, HashToken(newRefreshToken));

    var newAccessToken = GenerateJwt(user);

    Response.Cookies.Append("refreshToken", newRefreshToken, cookieOptions);

    return Ok(new { accessToken = newAccessToken });
}

Token Rotation (Highly Recommended)

Every refresh:

  • Old refresh token → revoked
  • New refresh token → issued

Why?

  • Prevents replay attacks
  • Stops stolen token reuse

Secure Session Management Tips

✅ Best Practices

  • Use HTTPS only
  • Store refresh token in HttpOnly cookie
  • Short access token lifespan
  • Rotate refresh tokens
  • Log refresh attempts
  • Revoke all tokens on logout

Logout Implementation

[HttpPost("logout")]
public IActionResult Logout()
{
    var refreshToken = Request.Cookies["refreshToken"];
    RevokeRefreshToken(refreshToken);

    Response.Cookies.Delete("refreshToken");
    return Ok();
}

Common Mistakes to Avoid

❌ Storing refresh tokens in localStorage
❌ Long-lived access tokens
❌ No token revocation
❌ Reusing refresh tokens
❌ Not hashing tokens in DB

Real-World Use Cases

  • Banking apps
  • SaaS dashboards
  • Mobile apps
  • Admin panels
  • Social platforms
0 Comments