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:

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:

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

Code
  POST /api/auth/login

Sample Controller Code

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

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

Response

Code
{
  "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

Code
Authorization: Bearer <access_token>

Protected API

Code
[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

Code
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

Code
POST /api/auth/refresh

Controller Code

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
    });
}

Step 5: Access API Using New Token

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

Request Header

Code
Authorization: Bearer <new_access_token>

Refresh Token Rotation

Refresh token rotation means:

This improves security and prevents token reuse.

Why Rotation Is Important

Step 6: Logout – Revoke Refresh Token

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

Logout API

Code
POST /api/auth/logout

Controller Code

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

After logout:

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

This screenshot shows the Login API call executed from Swagger.

1_FirstLogin

2. Check API Call – Authorization with Valid JWT Token

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

Code
Authorization: Bearer <access_token>

3. Check API Call After Token Expired – Unauthorized Response

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

Code
401 Unauthorized

4. Refresh Token API Call – Generate New JWT Token

This screenshot shows the Refresh Token API being called.

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.

Confirms that the refreshed token is valid and usable.

4_Access_API_After_NewGenerated_Token_By_RefreshToken

Best Practices

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