Skip to content

Excessive Token Refresh in includeBearerTokenInterceptor #659

@gaetanBloch

Description

@gaetanBloch

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:

  1. 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'
      }]
    }
  ]
};
  1. 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);
};
  1. Navigate through the application and observe the console
  2. Notice that updateToken() is called with undefined on 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:

  1. Performance degradation: Unnecessary network requests for token refresh
  2. Increased server load: Auth server receives excessive refresh requests
  3. Poor user experience: Potential delays due to constant token refreshes
  4. Token rotation issues: Rapid token rotation can cause issues with concurrent requests

Related Issues

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.

Metadata

Metadata

Labels

need-investigationNeeds more investigation to identify if it is a bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions