Blazor WebAssembly + Keycloak: A Deep Dive into Access Tokens, Refresh Tokens, and Session…
Authentication is usually the easy part.
Blazor WebAssembly + Keycloak: A Deep Dive into Access Tokens, Refresh Tokens, and Session Expiration

Authentication is usually the easy part.
You configure OpenID Connect, redirect users to Keycloak, receive an access token, and everything appears to work.
Then an hour later your API starts returning 401 Unauthorized.
The user refreshes the page and suddenly everything works again.
Or worse, they stay logged into the application for hours after their Keycloak session should have expired.
This is where many Blazor WebAssembly applications become unreliable.
The problem usually isn’t Keycloak or Blazor. It’s a misunderstanding of how access tokens, refresh tokens, and browser sessions work together.
This article focuses entirely on that lifecycle.
We’ll answer questions like:
- When should a refresh token be used?
- Does Blazor automatically refresh access tokens?
- Should a WebAssembly application store refresh tokens?
- What happens when a refresh token expires?
- How do you force the user to log out?
- How should token expiration be handled in production?
This isn’t another authentication tutorial. It’s the part most tutorials skip.
Understanding the token lifecycle
When a user signs in, Keycloak doesn’t return a single token.
It returns several.
Login
+----------------+
| Keycloak |
+--------+-------+
|
|
-------------------------
| | |
| | |
Access Token ID Token Refresh Token
Each serves a different purpose.
Access Token
The access token is sent to your APIs.
It proves that the user is authenticated and contains claims such as:
- user id
- username
- roles
- permissions
- expiration time
Typical lifetime:
5-15 minutes
The shorter, the better.
If someone steals an access token, they can only use it until it expires.
ID Token
The ID token is for the client application.
It describes the authenticated user.
Typical claims include:
{
"name": "John Doe",
"preferred_username": "john.doe",
"email": "john@example.com"
}
The ID token is not intended for API authorization.
Your APIs should validate access tokens only.
Refresh Token
The refresh token exists for one reason:
Obtain a new access token without asking the user to log in again.
Instead of redirecting the browser to the login page every few minutes, the client sends the refresh token to Keycloak.
Keycloak validates it and returns a brand new access token.
Access Token expires
│
▼
Refresh Token
│
▼
Keycloak validates
│
▼
New Access Token
The user never notices.
Why access tokens should expire quickly
A common mistake is increasing the access token lifetime to several hours.
It feels convenient.
It also increases the impact of a stolen token.
A better configuration is:
Access Token
5-15 minutes
Refresh Token
30-60 minutes
SSO Session
Several hours
This keeps access tokens short-lived while allowing users to stay signed in.
Does Blazor WebAssembly automatically refresh tokens?
This is where confusion begins.
Many developers expect this code:
var result = await TokenProvider.RequestAccessToken();
to always return a fresh access token.
It doesn’t.
IAccessTokenProvider manages access token acquisition according to the authentication library and the identity provider. If a valid access token is available, it returns it. When the token is no longer usable, the library may perform a silent authentication flow if the provider supports it. Whether a refresh token is used directly depends on the underlying OIDC implementation and configuration.
This distinction matters because many samples assume “automatic refresh” means “the client exchanges a refresh token itself.” In reality, the authentication library abstracts that process.
Should a Blazor WASM application use refresh tokens directly?
Usually, no.
Remember where Blazor WebAssembly runs.
Inside the browser.
Anything stored in the browser can potentially be accessed by malicious JavaScript if your application is vulnerable to XSS.
A refresh token is much more valuable than an access token because it can be exchanged for new access tokens repeatedly until it expires or is revoked.
For this reason, many teams avoid exposing long-lived refresh tokens to browser code.
Instead, they rely on standard OIDC browser flows, which obtain new tokens through the identity provider without requiring the application to manually manage refresh tokens.
If your architecture requires refresh tokens in the browser, keep their lifetime short and enable refresh token rotation in Keycloak.
What is refresh token rotation?
Without rotation:
Refresh Token A
│
▼
New Access Token
Refresh Token A
│
▼
New Access Token
Refresh Token A
│
▼
New Access Token
The same refresh token remains valid until it expires.
If someone steals it, they can continue obtaining new access tokens.
With rotation enabled:
Refresh Token A
│
▼
New Access Token
New Refresh Token B
Refresh Token A
❌ Invalid
Every successful refresh invalidates the previous refresh token and issues a new one.
Keycloak supports this through its realm token settings.
For public browser applications, refresh token rotation is strongly recommended.
What happens when the refresh token expires?
Eventually every refresh token expires.
At that point Keycloak refuses to issue another access token.
The client can no longer authenticate silently.
The only option is to sign in again.
The sequence looks like this.
Access Token expired
│
▼
Refresh Token expired
│
▼
Keycloak returns
invalid_grant
│
▼
User must authenticate again
This is expected behavior.
Your application should treat it as the end of the user’s authenticated session.
Detecting session expiration
The simplest approach is to react when token acquisition fails.
@inject IAccessTokenProvider TokenProvider
@inject NavigationManager Navigation
var result = await TokenProvider.RequestAccessToken();
if (!result.TryGetToken(out _))
{
Navigation.NavigateTo("authentication/login");
}
If the identity provider cannot provide a usable access token, redirect the user through the login flow.
Avoid retrying indefinitely. A failed token request is usually a signal that user interaction is required.
Handling 401 responses
Never assume the access token is valid forever.
Every API request can fail.
A common pattern is a custom DelegatingHandler.
public class UnauthorizedHandler : DelegatingHandler
{
private readonly NavigationManager _navigation;
public UnauthorizedHandler(NavigationManager navigation)
{
_navigation = navigation;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response =
await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
_navigation.NavigateTo("authentication/logout");
}
return response;
}
}
If the API rejects the token, the application immediately terminates the authenticated session instead of leaving the user in an inconsistent state.
This also covers scenarios where an administrator revokes the user’s session in Keycloak.
Logging out when the refresh token expires
A common requirement is:
“As soon as the refresh token expires, log the user out automatically.”
The important detail is that the browser has no event that says, “your refresh token just expired.”
The application only discovers this when it tries to obtain another access token or when an API rejects the existing one.
The flow looks like this.
User works normally
│
▼
Access Token expires
│
▼
Application requests new token
│
▼
Refresh Token expired
│
▼
Keycloak rejects request
│
▼
Logout
From the user’s perspective, the logout happens the next time the application needs authentication.
Can you predict expiration?
Yes.
JWTs contain an exp claim.
{
"exp": 1735142382
}
You can inspect this claim to determine when the access token expires.
However, avoid using it as your primary mechanism.
Clock skew, session revocation, and administrator actions can invalidate a token before its expiration time.
The identity provider and your API remain the source of truth.
Keycloak session settings matter
Token lifetimes are only one part of the picture.
Keycloak also maintains user sessions.
The most important settings are:
- Access Token Lifespan
- Client Session Idle
- Client Session Max
- SSO Session Idle
- SSO Session Max
Consider this configuration.
Access Token
10 minutes
Client Session Idle
30 minutes
SSO Session Idle
8 hours
The access token expires every ten minutes.
The user can still receive new access tokens because the Keycloak session remains active.
Once the session expires, silent authentication is no longer possible and the next authentication request requires the user to sign in again.
Design these values together rather than changing them independently.
Avoid these common mistakes
Using the ID token for API authorization
Always send the access token to your APIs.
The ID token describes the user.
The access token authorizes requests.
Those are different responsibilities.
Storing tokens yourself
Avoid storing access tokens or refresh tokens manually in localStorage.
Let the authentication library manage token acquisition and lifetime whenever possible.
The less token handling code you write, the fewer security problems you introduce.
Ignoring 401 responses
A 401 Unauthorized response is part of the authentication lifecycle.
Handle it intentionally.
Redirect the user through authentication or logout instead of leaving the UI in a partially authenticated state.
Making access tokens long-lived
Long-lived access tokens reduce security without improving the user experience.
Short-lived access tokens combined with session-based reauthentication provide a much better balance between usability and security.
Forgetting server-side validation
The Blazor application should never decide whether a request is authorized.
Every API endpoint must validate the JWT independently.
Even if someone bypasses your UI completely, the API remains protected.
Building a production-ready authentication flow
A robust Blazor WebAssembly application typically follows this sequence.
User signs in
│
▼
Access Token issued
│
▼
API requests
│
▼
Access Token expires
│
▼
Authentication library attempts to obtain a usable token
│
▼
Success?
│ │
Yes No
│ │
▼ ▼
Continue Redirect to login
Notice what is missing.
No custom timer.
No background polling.
No manual refresh loop.
The application requests tokens only when it needs them and reacts appropriately when authentication can no longer be maintained.
Final thoughts
Token refresh isn’t about keeping an access token alive forever. It’s about maintaining a secure, short-lived authentication model while minimizing interruptions for the user.
For Blazor WebAssembly, that means embracing the browser-oriented OpenID Connect flow instead of trying to reproduce server-side authentication patterns in JavaScript. Let the authentication library manage token acquisition, keep access tokens short-lived, enable refresh token rotation if refresh tokens are issued to the client, and treat failed token acquisition or 401 Unauthorized responses as the signal that the authenticated session has ended.
Finally, remember that authentication is only one layer of your security model. Your ASP.NET Core API must validate every access token, your Keycloak realm should enforce sensible session limits, and your application should always be prepared for sessions to end unexpectedly due to expiration, revocation, or administrative action. Applications that handle those transitions gracefully are the ones users trust in production.
메타데이터
- post_id
- 50694ac094ca
- slug
- blazor-webassembly-keycloak-a-deep-dive-into-access-tokens-refresh-tokens-and-session-50694ac094ca
- url
- https://medium.com/@norbertmarkiel/blazor-webassembly-keycloak-a-deep-dive-into-access-tokens-refresh-tokens-and-session-50694ac094ca
- canonical_url
- https://medium.com/@norbertmarkiel/blazor-webassembly-keycloak-a-deep-dive-into-access-tokens-refresh-tokens-and-session-50694ac094ca
- author_url
- https://medium.com/@norbertmarkiel
- status
- ok
- fetched_at
- 2026-07-14 00:39:24