angular-auth-oidc-client: Bug: id token is erased when the server does not send a new idtoken on refresh

What Version of the library are you using? 13.1.0

Question I am trying to get refresh process working with a server that give no id token with the refresh answer:

{access_token: "ZEPGAiCD01MSG5qleO9gQzjD46c9D9", expires_in: 60, token_type: "Bearer",…}
access_token: "ZEPGAiCD01MSG5qleO9gQzjD46c9D9"
expires_in: 60
refresh_token: "hc3Crg96ehnOblKUImHAeGRgamPvf5"
scope: "openid read write"
token_type: "Bearer"

As explained in the documentation, I set the disableRefreshIdTokenAuthTimeValidation option to false and I manually call

this._oidcSecurityService.forceRefreshSession().subscribe((result:any) => {
                  if (!result || result == null) {
                    console.error('Access token refresh process failed -> Logoff requested !');
                    ...
                  }
                });

I always get an error even if I can see in the log: image

Note that access token expiration is set to 60s (for test) and id token expiration to 10s in the server.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 40 (10 by maintainers)

Most upvoted comments

I have also encountered this issue, here is the culprit: https://github.com/damienbod/angular-auth-oidc-client/blob/9fc89c8a6ce8af4a2abd528c484326a6f0fa5f64/projects/angular-auth-oidc-client/src/lib/flows/callback-handling/history-jwt-keys-callback-handler.service.ts#L34

When id_token is missing in refresh response, existing id_token will be discarded. @damienbod can this be patched by preserving current id_token, or will it break something else?

As a workaround without modifying package code I have patched id_token on save if it suddenly disappears, something like this:

import { Injectable } from '@angular/core';
import { AbstractSecurityStorage } from 'angular-auth-oidc-client';
import * as _ from 'lodash';

@Injectable({
  providedIn: 'root',
})
export class CustomSecurityStorage implements AbstractSecurityStorage {
  read(key: string) {
    return sessionStorage.getItem(key);
  }

  write(key: string, value: any): void {
    const currentDataRaw = sessionStorage.getItem(key);
    if (!currentDataRaw) {
      sessionStorage.setItem(key, value);
      return;
    }

    // Patch id_token when missing in token refresh response
    const currentDataParsed = JSON.parse(currentDataRaw) || {};
    const newDataParsed = JSON.parse(value) || {};

    const currentIdToken = _.get(currentDataParsed, 'authnResult.id_token');
    const newIdToken = _.get(newDataParsed, 'authnResult.id_token');
    const newAccessToken = _.get(newDataParsed, 'authnResult.access_token');

    // If id_token is currently present, but missing from refresh response, patch it with old value
    const newDataPatched =
      newAccessToken && currentIdToken && !newIdToken
        ? _.set(newDataParsed, 'authnResult.id_token', currentIdToken)
        : newDataParsed;

    sessionStorage.setItem(key, JSON.stringify(newDataPatched));
  }

  remove(key: string): void {
    sessionStorage.removeItem(key);
  }

  clear(): void {
    sessionStorage.clear();
  }
}

and then provide in app module

providers: [
    {
      provide: AbstractSecurityStorage,
      useClass: CustomSecurityStorage,
    },
  ],

Do you need help with the patch?

@bgerhards I will try to setup a OIDC Code flow with PKCE and refresh tokens using “Micro Focus Access Manager”

No promises when, never used this IDP before.

@damienbod I can give you a clientID and a test user in our dev Microfocus access manager system if you would like to test it that way.

Would the title “id token is erased when the server does not send a new idtoken on refresh” be a better fit?

I sent it to your email address. Let me know if you need anything else.

We are currently working on this. This is also connected to #1552 which we want to solve as well. https://github.com/damienbod/angular-auth-oidc-client/pull/1571 (is still WIP)

I solved it with this interceptor 😄

@Injectable()
export class MissingIdTokenDuringRefreshCompensationInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    if (
      request.url.includes('/token') &&
      request.method === 'POST' &&
      (request.body as string).includes('grant_type=refresh_token')
    ) {
      return next.handle(request).pipe(
        tap((response: any) => {
          if (response.body) {
            response.body.id_token = response.body.id_token ?? response.body.access_token;
          }
        })
      );
    }
    return next.handle(request);
  }
}

as the id_token is not needed in my case anyway I can live with the access_token there as a placeholder.

@damienbod I sent you an email with the details. Let me know if you don’t get that or have questions. I forgot to add the scopes to the email, but you can use “openid profile email roles”.

Micro Focus Access Manager is apparently another name for it? Micro Focus owns NetIQ. If we are incorrect on Access Manager being another alias, please send correction.

@agardiol This should work, I will test this again , doing a new release now

Greetings Damien