ASP.NET Core  

Refresh Token Implementation in ASP.NET Core

Authentication is an important part of any modern application. In ASP.NET Core, JWT (JSON Web Token) is commonly used to secure APIs. However, JWT tokens usually have a short expiry time.

To avoid forcing users to log in again and again, we use refresh tokens.

This article explains:

  • What refresh tokens are

  • How refresh token rotation works

  • How to revoke refresh tokens on logout

  • A simple real-time flow using ASP.NET Core 8.0

Why Do We Need Refresh Tokens?

JWT tokens are short-lived for security reasons.

If a token is stolen, it should not work for a long time.

Example:

  • JWT token expires in 2 minutes

  • Refresh token expires in 7 days

When the JWT expires, the client uses the refresh token to get a new JWT without logging in again.

Access Token vs Refresh Token

Token TypePurposeLifetime
Access Token (JWT)Access protected APIsShort (2 minutes)
Refresh TokenGenerate new access tokenLong (7 days)

Authentication Flow

This section shows how authentication works step by step, along with small code examples.

Step 1: Login – Generate Access Token and Refresh Token

The user logs in using valid credentials.

The API generates and returns both tokens.

Login API

  POST /api/auth/login

Sample Controller Code

[HttpPost("login")]
public IActionResult Login()
{
    var accessToken = _tokenService.GenerateAccessToken("admin");
    var refreshToken = _tokenService.GenerateRefreshToken();

    return Ok(new
    {
        accessToken,
        refreshToken
    });
}

Response

{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "b4e3d8c1-9c77-4d..."
}

Step 2: Access Protected API Using JWT

The client sends the JWT token in the request header to access secured APIs.

Request Header

Authorization: Bearer <access_token>

Protected API

[Authorize]
[HttpGet("secure")]
public IActionResult SecureApi()
{
    return Ok("API accessed using valid JWT token");
}

Step 3: JWT Token Expired

After the access token expires (2 minutes), the API rejects the request.

Response

401 Unauthorized

Note: At this point, the client should not ask the user to log in again.

Step 4: Refresh Token – Generate New Access Token

The client calls the refresh API and sends the refresh token.

Refresh API

POST /api/auth/refresh

Controller Code

[HttpPost("refresh")]
public IActionResult Refresh([FromBody] string refreshToken)
{
    if (!_tokenService.ValidateRefreshToken(refreshToken))
        return Unauthorized();

    var newAccessToken = _tokenService.GenerateAccessToken("admin");
    var newRefreshToken = _tokenService.RotateRefreshToken(refreshToken);

    return Ok(new
    {
        accessToken = newAccessToken,
        refreshToken = newRefreshToken
    });
}
  • Old refresh token is invalid

  • New refresh token is issued (rotation)

Step 5: Access API Using New Token

The client replaces the expired JWT with the newly generated one.

Request Header

Authorization: Bearer <new_access_token>

Refresh Token Rotation

Refresh token rotation means:

  • Old refresh token becomes invalid after use

  • A new refresh token is issued every time

This improves security and prevents token reuse.

Why Rotation Is Important

  • Prevents replay attacks

  • Limits damage if a token is leaked

  • Follows industry standards

Step 6: Logout – Revoke Refresh Token

When the user logs out, the refresh token is revoked.

Logout API

POST /api/auth/logout

Controller Code

[HttpPost("logout")]
public IActionResult Logout([FromBody] string refreshToken)
{
    _tokenService.RevokeRefreshToken(refreshToken);
    return Ok("Logged out successfully");
}

After logout:

  • Refresh token is invalid

  • Token refresh is no longer possible

  • User must log in again

1. Login API Call – Generate Access Token and Refresh Token

This screenshot shows the Login API call executed from Swagger.

  • API: POST /api/auth/login

  • Response contains:

    • JWT access token

    • Refresh token

  • This confirms that login is successful and tokens are generated.

1_FirstLogin

2. Check API Call – Authorization with Valid JWT Token

This screenshot shows the Check API being accessed using a valid JWT token.

  • API: GET /api/check/secure

  • Authorization header includes:

Authorization: Bearer <access_token>
  • API returns 200 OK

  • Confirms that authorization works correctly with a valid JWT.

    4_Access_API_After_NewGenerated_Token_By_RefreshToken

3. Check API Call After Token Expired – Unauthorized Response

This screenshot shows the same Check API call after the JWT token has expired.

  • API: GET /api/check/secure

  • JWT token is expired

  • API returns:

401 Unauthorized
  • Confirms that expired tokens are rejected by the system.

    2_AccessAPI_TokenExpired

4. Refresh Token API Call – Generate New JWT Token

This screenshot shows the Refresh Token API being called.

  • API: POST /api/auth/refresh

  • Refresh token is sent in the request body

  • Response contains:

    • New JWT access token

    • New refresh token (rotation)

This confirms that the refresh token flow works as expected.

3_RefreshToken_API_Call

5. Check API Call – Authorization with New JWT Token

This screenshot shows the Check API being accessed again using the newly generated JWT token.

  • API: GET /api/check/secure

  • Authorization header includes the new JWT

  • API returns 200 OK

Confirms that the refreshed token is valid and usable.

4_Access_API_After_NewGenerated_Token_By_RefreshToken

Best Practices

  • Keep access tokens short-lived

  • Always rotate refresh tokens

  • Revoke refresh tokens on logout

  • Use HTTPS only

  • Store refresh tokens securely (database or cache)

Conclusion

Refresh tokens help maintain a good balance between security and user experience.

Using refresh token rotation and logout revocation ensures that sessions are safe and controlled.

ASP.NET Core 8.0 makes it easy to implement this flow in a clean and structured way.

This approach is suitable for internal applications, external APIs, and modern web systems.

Source Code

You can find the complete working demo here:

GitHub Repository: https://github.com/vishal079/JwtRefreshTokenDemo