-
-
Notifications
You must be signed in to change notification settings - Fork 312
Description
Issue Title
includeBearerTokenInterceptor causes excessive token refreshes due to missing minValidity parameter
Description
Bug Report
- bug report -> please search for issues before submitting
- feature request
Current behavior:
The includeBearerTokenInterceptor in keycloak-angular v19.0.2 triggers a token refresh on every HTTP request, regardless of the token's actual expiration time. This happens because the interceptor calls keycloak.updateToken() without any minValidity parameter, effectively passing undefined.
When updateToken() is called without a minValidity parameter (or with undefined), Keycloak interprets this as "refresh the token immediately", causing unnecessary token refreshes on every HTTP request.
Expected behavior:
The interceptor should only refresh the token when it's about to expire (e.g., within 5-30 seconds of expiration), not on every HTTP request.
Reproduction steps:
- Configure keycloak-angular v19+ with
includeBearerTokenInterceptor:
export const appConfig: ApplicationConfig = {
providers: [
provideKeycloak({
config: {
url: 'https://auth.example.com',
realm: 'my-realm',
clientId: 'my-client'
},
initOptions: {
onLoad: 'check-sso',
checkLoginIframe: true
}
}),
provideHttpClient(
withInterceptors([includeBearerTokenInterceptor])
),
{
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
useValue: [{
urlPattern: /^(.*)?$/i,
bearerPrefix: 'Bearer'
}]
}
]
};- Add debug logging to monitor token refresh calls:
// Add to browser console
const originalUpdateToken = window.keycloak.updateToken;
window.keycloak.updateToken = function(minValidity) {
console.log('Token refresh called with minValidity:', minValidity);
console.trace('Call stack');
return originalUpdateToken.call(this, minValidity);
};- Navigate through the application and observe the console
- Notice that
updateToken()is called withundefinedon every HTTP request
Debug trace output:
Token refresh called with minValidity: undefined
Called from: at conditionallyUpdateToken (keycloak-angular.js:1030:27)
Time: 11:33:39
Root Cause Analysis
In the current implementation (v19.0.2), the conditionallyUpdateToken function in the interceptor:
const conditionallyUpdateToken = async (req, keycloak, { shouldUpdateToken = (_) => true }) => {
if (shouldUpdateToken(req)) {
return await keycloak.updateToken().catch(() => false); // <-- No minValidity parameter!
}
return true;
};This results in keycloak.updateToken() being called without any parameter, which triggers an immediate token refresh regardless of the token's actual expiration time.
Proposed Solution
The conditionallyUpdateToken function should accept a minValidity parameter and pass it to updateToken():
const conditionallyUpdateToken = async (req, keycloak, { shouldUpdateToken = (_) => true, minValidity = 5 }) => {
if (shouldUpdateToken(req)) {
return await keycloak.updateToken(minValidity).catch(() => false);
}
return true;
};And the condition interfaces should include an optional minValidity field:
interface IncludeBearerTokenCondition extends AuthBearerCondition {
urlPattern: RegExp;
httpMethods?: string[];
minValidity?: number; // Add this field
}Environment
- keycloak-angular version: 19.0.2
- keycloak-js version: 26.1.2
- Angular version: 19.1.4
- Browser: Chrome/Safari/Firefox (all affected)
Impact
This bug causes:
- Performance degradation: Unnecessary network requests for token refresh
- Increased server load: Auth server receives excessive refresh requests
- Poor user experience: Potential delays due to constant token refreshes
- Token rotation issues: Rapid token rotation can cause issues with concurrent requests
Related Issues
- Configure updateToken minValidity in HTTP Interceptor #557 - Requests the ability to configure
minValidity, but doesn't mention the bug of passingundefined
Workaround
Until this is fixed, users can implement a custom interceptor that properly handles token refresh:
export const authTokenInterceptor: HttpInterceptorFn = (req, next) => {
const keycloak = inject(Keycloak);
if (!keycloak.authenticated) {
return next(req);
}
const tokenExp = keycloak.tokenParsed?.exp ? keycloak.tokenParsed.exp * 1000 : 0;
const now = Date.now();
const timeToExpiry = tokenExp - now;
const needsRefresh = timeToExpiry < 5000; // 5 seconds buffer
if (needsRefresh && tokenExp > 0) {
return from(keycloak.updateToken(30)).pipe( // Pass proper minValidity
switchMap(() => {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${keycloak.token}` }
});
return next(authReq);
})
);
} else {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${keycloak.token}` }
});
return next(authReq);
}
};Additional Context
This issue exists in both v19 and v20 of keycloak-angular. While issue #557 requests the ability to configure minValidity, it doesn't address the fact that the current implementation is effectively broken due to passing undefined, which causes excessive refreshes.
Note to maintainers: This is different from #557 which is a feature request. This is a bug report about the interceptor causing excessive token refreshes due to the missing parameter, which significantly impacts application performance.