Token expiration handling with Angular BFF Client and Downstream APIs
Problem
When the access_token
and refresh_token
expire, but the authentication cookie is still valid, the user will appear authenticated, yet calls to downstream APIs will start to fail with 401 Unauthorized
. See React to back-end changes
Alternative solutions
To avoid this mismatch there are several possible solutions:
- Use CookieEvent to validate the refresh token: To ensure the session remains valid from the API’s perspective, proactively validate the refresh_token.
- Implement a global exception handler for downstream API errors: Set up a global exception handler to capture exceptions from downstream APIs. Specifically, handle 401 responses by throwing a 401 exception, which can then be handled appropriately by the client.
- Create custom middleware for token management: Develop middleware that checks the token’s expiration before forwarding requests to downstream APIs. If the token has expired, refresh it automatically before proceeding.
Use CookieEvent to validate the refresh token
Override the ValidatePrincipal
method on the CookieEvent and check the status of both the access_token
and refresh_token
. If the access_token
is expired, attempt to use the refresh_token
to obtain a new one. If that fails, reject the principal.
🔒 This logic must be combined with proper cookie expiration settings using
ExpireTimeSpan
to manage session longevity consistently.
See sample code in Angular and Blazor
Do the following steps:
Step 1: Change Cookie expiration time
ExpireTimeSpan should be set out from access_token and refresh_token lifetime. This is to ensure that the cookie is not expired after the refresh token is expired.
Sample code
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromSeconds(90);
})
...
Step 2: Add AddOpenIdConnectCookieOptions to the pipeline
You can use Fhi.Authentication.Extensions
package to validate the token expiration.
The AddOpenIdConnectCookieOptions
will add ValidatePrincipal
event that checks if token is expired, see implementation and handle SignOut
OpenIdConnectCookieEventsForApi
builder.Services.AddOpenIdConnectCookieOptions();
Create custom middleware for token management
The sample below uses Duende accesstoken management GetUserAccessTokenAsync
extension
public class TokenExpirationMiddleware
{
private readonly RequestDelegate _next;
public TokenExpirationMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var user = context.User;
if (user.Identity is not null && user.Identity.IsAuthenticated)
{
var userToken = await context.GetUserAccessTokenAsync();
if (userToken.IsError)
{
//Handle token expiration in your application in your preffered way
//await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
await _next(context);
}
}