angular-auth-oidc-client
angular-auth-oidc-client copied to clipboard
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:
Note that access token expiration is set to 60s (for test) and id token expiration to 10s in the server.
Thanks by advance !
@damienbod Sorry to insist. Do you have any answer to this question ? Thanks by advance.
@agardiol This should work, I will test this again , doing a new release now
Greetings Damien
Update for v14.0:
The behavior is still the same with the new version 14. The new access token is well updated in the authResult area in the session storage, but the returned object of forceRefreshSession().subscribe((loginResponse: LoginResponse)
is still null even if the process succeed.
Note also that even if the access_token is updated in the session storage, it is no more injected in the next backend request as authorization Bearer. The refresh process is so not fully successful.
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?
Thanks for this, looks good and needed. @FabianGosebrink?
Not sure if this is the best way of solving this.
Looks like this was given the investigate label back in May. A workaround has been given but it looks like there is some concern. Anything new on this issue or the others that are related to it @damienbod , @FabianGosebrink? I appreciate your time and I hope all is well!
Is it normal or a misconfiguration that when refreshing the token, no ID token is being sent with?
It depends on the type of the backend server. By example a standard Django configuration does not send ID token when refreshing session.
Another specific example is NetIQ: https://www.netiq.com/documentation/access-manager-45-developer-documentation/oauth-application-developer-guide/data/managing-tokens.html
I will try to setup an IDP to test this. All the IDPs I use, return the id_token
These 2 IDPs are not certified, are these servers OIDC conform?
https://openid.net/certification/
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.
@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.
Would the title "id token is erased when the server does not send a new idtoken on refresh" be a better fit?
Is it normal or a misconfiguration that when refreshing the token, no ID token is being sent with? From official OpenID connect core final documention (https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse): ID Token is not mandatory
FYI This issue is a duplicate of https://github.com/damienbod/angular-auth-oidc-client/issues/1039#issuecomment-979481866 which was closed for no reason at all.
@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.
@jschutte27 Yes please and the config to connect as well?
@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".
We have the same problem, token endpoint does not return an id token for a refresh token. After an automatic or manual refresh the session is broken.
Seems like the id_token should be validated only once after the initial login. See: https://stackoverflow.com/a/25810348
A simple fix would be to store the id_token like the access_token in a separate field.
Following patch helps us when enableIdTokenExpiredValidationInRenew is false
diff --git a/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts b/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts
index 9aa0b6f7..4900c506 100644
--- a/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts
+++ b/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts
@@ -54,6 +54,11 @@ export class AuthStateService {
): void {
this.loggerService.logDebug(currentConfig, `storing the accessToken '${accessToken}'`);
+ if (authResult.id_token != null) {
+ this.loggerService.logDebug(currentConfig, `storing the id token '${authResult.id_token}'`);
+ this.storagePersistenceService.write('authzIdTokenData', accessToken, currentConfig);
+ }
+
this.storagePersistenceService.write('authzData', accessToken, currentConfig);
this.persistAccessTokenExpirationTime(authResult, currentConfig);
this.setAuthenticatedAndFireEvent(allConfigs);
diff --git a/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts b/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts
index 5865d5c2..ed3f19d7 100644
--- a/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts
+++ b/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts
@@ -5,6 +5,7 @@ import { BrowserStorageService } from './browser-storage.service';
export type StorageKeys =
| 'authnResult'
| 'authzData'
+ | 'authzIdTokenData'
| 'access_token_expires_at'
| 'authWellKnownEndPoints'a
| 'userData'
@@ -67,6 +68,7 @@ export class StoragePersistenceService {
resetAuthStateInStorage(config: OpenIdConfiguration): void {
this.remove('authzData', config);
+ this.remove('authzIdTokenData', config);
this.remove('reusable_refresh_token', config);
this.remove('authnResult', config);
}
@@ -76,7 +78,7 @@ export class StoragePersistenceService {
}
getIdToken(config: OpenIdConfiguration): string {
- return this.read('authnResult', config)?.id_token;
+ return this.read('authzIdTokenData', config);
}
getRefreshToken(config: OpenIdConfiguration): string {
Following patch helps us when enableIdTokenExpiredValidationInRenew is false
diff --git a/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts b/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts index 9aa0b6f7..4900c506 100644 --- a/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/auth-state/auth-state.service.ts @@ -54,6 +54,11 @@ export class AuthStateService { ): void { this.loggerService.logDebug(currentConfig, `storing the accessToken '${accessToken}'`); + if (authResult.id_token != null) { + this.loggerService.logDebug(currentConfig, `storing the id token '${authResult.id_token}'`); + this.storagePersistenceService.write('authzIdTokenData', accessToken, currentConfig); + } + this.storagePersistenceService.write('authzData', accessToken, currentConfig); this.persistAccessTokenExpirationTime(authResult, currentConfig); this.setAuthenticatedAndFireEvent(allConfigs); diff --git a/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts b/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts index 5865d5c2..ed3f19d7 100644 --- a/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/storage/storage-persistence.service.ts @@ -5,6 +5,7 @@ import { BrowserStorageService } from './browser-storage.service'; export type StorageKeys = | 'authnResult' | 'authzData' + | 'authzIdTokenData' | 'access_token_expires_at' | 'authWellKnownEndPoints'a | 'userData' @@ -67,6 +68,7 @@ export class StoragePersistenceService { resetAuthStateInStorage(config: OpenIdConfiguration): void { this.remove('authzData', config); + this.remove('authzIdTokenData', config); this.remove('reusable_refresh_token', config); this.remove('authnResult', config); } @@ -76,7 +78,7 @@ export class StoragePersistenceService { } getIdToken(config: OpenIdConfiguration): string { - return this.read('authnResult', config)?.id_token; + return this.read('authzIdTokenData', config); } getRefreshToken(config: OpenIdConfiguration): string {
This flag does not seem to work for me. The library just stops adding the access token to the header after the refresh call without id_token.
This only works with the patch, without the id_token is lost after the refresh call.
I solved it with this interceptor :D
@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.
Basically we can fix this asking for an idtoken in the callback response, or am I missing something?
Like in projects\angular-auth-oidc-client\src\lib\flows\callback-handling\history-jwt-keys-callback-handler.service.ts
callbackHistoryAndResetJwtKeys(
callbackContext: CallbackContext,
config: OpenIdConfiguration,
allConfigs: OpenIdConfiguration[]
): Observable<CallbackContext> {
if (!this.responseHasIdToken(callbackContext)) {
const existingIdToken = this.storagePersistenceService.getIdToken(config);
callbackContext.authResult.id_token = existingIdToken;
}
this.storagePersistenceService.write('authnResult', callbackContext.authResult, config);
/* ... */
}
Any thoughts?
In our case the idtoken will be expired by then. So if there will be a check after that callback it will fail.
Basically we can fix this asking for an idtoken in the callback response, or am I missing something?
Like in
projects\angular-auth-oidc-client\src\lib\flows\callback-handling\history-jwt-keys-callback-handler.service.ts
callbackHistoryAndResetJwtKeys( callbackContext: CallbackContext, config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[] ): Observable<CallbackContext> { if (!this.responseHasIdToken(callbackContext)) { const existingIdToken = this.storagePersistenceService.getIdToken(config); callbackContext.authResult.id_token = existingIdToken; } this.storagePersistenceService.write('authnResult', callbackContext.authResult, config); /* ... */ }
Any thoughts?
I was thinking that we could preserve the first original id_token in the sessionStorage after the token refresh call returns an answer without the id_token && if the enableIdTokenExpiredValidationInRenew
is set to false.