ASP.NET Core  

JWT Session 15: Implement Logout and Refresh Token Revocation in ASP.NET Core Web API

In the previous article, we implemented Refresh Token Authentication. Our SecureShop API now issues both an Access Token and a Refresh Token, allowing users to stay signed in without logging in repeatedly.

However, one important security question remains.

What happens when a user logs out?

If the refresh token remains valid after logout, anyone who possesses that token can continue generating new access tokens until it expires.

To prevent this, we need to revoke the refresh token.

In this article, you'll learn how logout works in a JWT-based application, why refresh token revocation is necessary, and how to implement it in ASP.NET Core Web API.

Implement Logout and Refresh Token Revocation in ASP.NET Core Web API

Why Isn't JWT Logout Enough?

Let's continue with our SecureShop API.

Suppose John logs in.

The API issues:

  • Access Token

  • Refresh Token

John clicks Logout.

If the application simply deletes the tokens from the browser, everything appears to be fine.

But imagine someone had already copied John's refresh token.

Even after logout, that copied refresh token could still be used to request new access tokens.

The server must therefore invalidate the refresh token during logout.

What Is Refresh Token Revocation?

Refresh Token Revocation means marking a refresh token as no longer valid.

Once revoked:

  • It cannot generate new access tokens.

  • Any future refresh requests are rejected.

  • The user must log in again.

This gives the server control over user sessions.

Authentication Flow Before Logout

Login
   │
   ▼
Access Token
Refresh Token
   │
   ▼
Access Protected APIs

Everything works normally while the refresh token remains valid.

Authentication Flow After Logout

User Clicks Logout
        │
        ▼
Delete Refresh Token
        │
        ▼
Refresh Token Revoked
        │
        ▼
Future Refresh Requests Rejected

The access token will naturally expire after its lifetime.

Since the refresh token has been revoked, the user cannot obtain a new access token.

Step 1: Create a Logout Endpoint

Create a new endpoint.

POST /api/auth/logout

The client sends its refresh token.

{
    "refreshToken": "Xy82GhL9..."
}

The API will use this token to identify the current session.

Step 2: Find the Refresh Token

Search the database.

var refreshToken = await _context.RefreshTokens
    .FirstOrDefaultAsync(x =>
        x.Token == request.RefreshToken);

If the token isn't found, return:

401 Unauthorized

Step 3: Revoke the Token

If the token exists, remove or revoke it.

Example:

_context.RefreshTokens.Remove(refreshToken);

await _context.SaveChangesAsync();

Some applications delete the token.

Others keep the record and mark it as revoked.

Both approaches prevent future use.

Step 4: Return Success

If revocation succeeds, return:

200 OK

The client should now remove both the access token and refresh token from its local storage or secure storage.

Internal Logout Flow

Client
   │
   │ Refresh Token
   ▼
Logout Endpoint
   │
   ▼
Database
   │
   ▼
Find Refresh Token
   │
   ▼
Delete / Revoke Token
   │
   ▼
Return Success

The session is now terminated on both the client and server.

Practical Example

Let's continue with the SecureShop API.

John logs in.

The server stores:

User : John

Refresh Token : Xy82GhL9...

Expires : 7 Days

John clicks Logout.

The application calls:

POST /api/auth/logout

The server deletes the stored refresh token.

Later, someone attempts:

POST /api/auth/refresh

using the old refresh token.

The API searches the database.

Token not found.

Response:

401 Unauthorized

A new access token is never generated.

Why Access Tokens Still Work for a Short Time

Many developers expect logout to immediately invalidate the access token.

However, JWT access tokens are self-contained.

Once issued, they remain valid until they expire.

For example:

  • Access Token Lifetime → 15 minutes

  • User logs out after 5 minutes

The access token may still be accepted for another 10 minutes.

This is why production systems usually:

  • Use short-lived access tokens.

  • Revoke refresh tokens immediately.

  • Issue new access tokens only through valid refresh tokens.

Security Best Practices

For a secure logout implementation:

  • Keep access tokens short-lived.

  • Store refresh tokens in the database.

  • Revoke refresh tokens during logout.

  • Remove client-side tokens after logout.

  • Use HTTPS for all authentication requests.

  • Rotate refresh tokens after each successful refresh.

These practices significantly reduce the risk of session hijacking.

Common Mistakes

Mistake 1: Deleting Tokens Only on the Client

Removing tokens from browser storage does not invalidate them on the server.

Always revoke the refresh token in the database.

Mistake 2: Expecting JWT Access Tokens to Be Revoked Automatically

A JWT cannot usually be revoked once issued.

Instead, keep its lifetime short and rely on refresh token revocation.

Mistake 3: Forgetting to Remove Client-Side Tokens

After a successful logout, the client should immediately delete both the access token and refresh token.

Mistake 4: Returning Success Without Revoking the Token

Returning 200 OK without actually removing or revoking the refresh token leaves the session active.

Always invalidate the refresh token before confirming logout.

Key Takeaways

  • Logging out in a JWT-based application mainly involves revoking the refresh token.

  • A revoked refresh token cannot generate new access tokens.

  • Store refresh tokens in the database so they can be validated and revoked.

  • JWT access tokens remain valid until they expire, so keep their lifetime short.

  • Always remove tokens from both the server and the client during logout.

Our SecureShop API now supports secure login, JWT authentication, ASP.NET Core Identity, refresh tokens, and proper logout with refresh token revocation. In the next article, we'll implement Global Exception Handling for Authentication and Authorization, providing consistent error responses for scenarios such as invalid tokens, expired tokens, unauthorized access, and forbidden requests.