Skip to content

Commit

Permalink
AAE-24139 Fix unauthorized user error when access token is not valid (#…
Browse files Browse the repository at this point in the history
…10012)

* AAE-24139 Bump angular-oauth2-oidc version to 15

* AAE-24139 Allow to set sessionCheckEnabled and clockSkewInSec properties

* AAE-24139 Update angular-oauth2-oidc version to 15 in the core deps

* AAE-24139 Remove authentication tokens when the token is no longer valid and reload the page to let oauth library refresh the token

* fix lint issue
  • Loading branch information
alep85 authored Sep 10, 2024
1 parent f1208d4 commit b20107f
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 16 deletions.
2 changes: 1 addition & 1 deletion lib/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
},
"dependencies": {
"cropperjs": "^1.5.13",
"angular-oauth2-oidc": "^13.0.1",
"angular-oauth2-oidc": "^15.0.1",
"angular-oauth2-oidc-jwks": "^17.0.2"
},
"peerDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions lib/core/src/lib/auth/models/oauth-config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ export interface OauthConfigModel {
redirectSilentIframeUri?: string;
refreshTokenTimeout?: number;
publicUrls: string[];
clockSkewInSec?: number;
sessionChecksEnabled?: boolean;
}
59 changes: 59 additions & 0 deletions lib/core/src/lib/auth/oidc/auth-config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,63 @@ describe('AuthConfigService', () => {
expect(service.loadAppConfig().postLogoutRedirectUri).toBe('http://localhost:3000/asd');
});
});

describe('clockSkewInSec', () => {
it('should return clockSkewInSec equal to 0', () => {
const expectedClockSkewInSec = 0;
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ clockSkewInSec: 0 } as any);
expect(service.loadAppConfig().clockSkewInSec).toBe(expectedClockSkewInSec);
});
it('should not return clockSkewInSec if is not defined', () => {
spyOnProperty(appConfigService, 'oauth2').and.returnValue({} as any);
expect(service.loadAppConfig().clockSkewInSec).toBeUndefined();
});

it('should not return clockSkewInSec if is undefined', () => {
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ clockSkewInSec: undefined } as any);
expect(service.loadAppConfig().clockSkewInSec).toBeUndefined();
});

it('should return empty object if clockSkewInSec is null', () => {
const mockOauth2Value = { clockSkewInSec: null } as any;
expect(service.getClockSkewInSec(mockOauth2Value)).toEqual({});
});

it('should return empty object if clockSkewInSec is a string', () => {
const mockOauth2Value = { clockSkewInSec: 'null' } as any;
expect(service.getClockSkewInSec(mockOauth2Value)).toEqual({});
});
});

describe('sessionChecksEnabled', () => {
it('should return sessionChecksEnabled equal to true', () => {
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ sessionChecksEnabled: true } as any);
expect(service.loadAppConfig().sessionChecksEnabled).toBeTrue();
});

it('should return sessionChecksEnabled equal to false', () => {
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ sessionChecksEnabled: false } as any);
expect(service.loadAppConfig().sessionChecksEnabled).toBeFalse();
});

it('should not return sessionChecksEnabled if is not defined', () => {
expect(service.getSessionCheckEnabled({} as any)).toEqual({});
});

it('should not return sessionChecksEnabled if is a string', () => {
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: 'fake' } as any)).toEqual({});
});

it('should not return sessionChecksEnabled if is undefined', () => {
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: undefined } as any)).toEqual({});
});

it('should not return sessionChecksEnabled if is null', () => {
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: null } as any)).toEqual({});
});

it('should not return sessionChecksEnabled if is a number', () => {
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: 666 } as any)).toEqual({});
});
});
});
15 changes: 14 additions & 1 deletion lib/core/src/lib/auth/oidc/auth-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AuthConfig } from 'angular-oauth2-oidc';
import { take } from 'rxjs/operators';
import { AppConfigService } from '../../app-config/app-config.service';
import { AUTH_MODULE_CONFIG, AuthModuleConfig } from './auth-config';
import { OauthConfigModel } from '../models/oauth-config.model';

/**
* Create auth configuration factory
Expand Down Expand Up @@ -51,6 +52,8 @@ export class AuthConfigService {
const origin = this.getLocationOrigin();
const redirectUri = this.getRedirectUri();
const customQueryParams = oauth2.audience ? { audience: oauth2.audience } : {};
const clockSkewInSec = this.getClockSkewInSec(oauth2);
const sessionChecksEnabled = this.getSessionCheckEnabled(oauth2);

return new AuthConfig({
...oauth2,
Expand All @@ -65,10 +68,20 @@ export class AuthConfigService {
dummyClientSecret: oauth2.secret || '',
logoutUrl: oauth2.logoutUrl,
customQueryParams,
...(oauth2.codeFlow && { responseType: 'code' })
...(oauth2.codeFlow && { responseType: 'code' }),
...clockSkewInSec,
...sessionChecksEnabled
});
}

getSessionCheckEnabled(oauth2: OauthConfigModel) {
return typeof oauth2.sessionChecksEnabled === 'boolean' ? { sessionChecksEnabled: oauth2.sessionChecksEnabled } : {};
}

getClockSkewInSec(oauth2: OauthConfigModel) {
return typeof oauth2.clockSkewInSec === 'number' ? { clockSkewInSec: oauth2.clockSkewInSec } : {};
}

getRedirectUri(): string {
// required for this package as we handle the returned token on this view, with is provided by the AuthModule
const viewUrl = `view/authentication-confirmation`;
Expand Down
23 changes: 23 additions & 0 deletions lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('RedirectAuthService', () => {
events: oauthEvents$,
configure: () => {},
hasValidAccessToken: jasmine.createSpy().and.returnValue(true),
hasValidIdToken: jasmine.createSpy().and.returnValue(true),
setupAutomaticSilentRefresh: () => {
mockOauthService.silentRefresh();
mockOauthService.refreshToken();
Expand All @@ -53,6 +54,7 @@ describe('RedirectAuthService', () => {

TestBed.inject(OAuthService);
service = TestBed.inject(RedirectAuthService);
spyOn(service, 'reloadPage').and.callFake(() => {});
spyOn(service, 'ensureDiscoveryDocument').and.resolveTo(true);
mockOauthService.getAccessToken = () => 'access-token';
});
Expand Down Expand Up @@ -93,4 +95,25 @@ describe('RedirectAuthService', () => {
expect(refreshTokenCalled).toBe(true);
expect(silentRefreshCalled).toBe(true);
});

it('should remove all auth items from the storage if access token is set and is not authenticated', () => {
mockOauthService.getAccessToken = () => 'access-token';
spyOnProperty(service, 'authenticated', 'get').and.returnValue(false);
(mockOauthService.events as Subject<OAuthEvent>).next({ type: 'discovery_document_loaded' } as OAuthEvent);

expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('access_token');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('access_token_stored_at');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('expires_at');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('granted_scopes');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_claims_obj');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_expires_at');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_stored_at');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('nonce');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('PKCE_verifier');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('refresh_token');
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('session_state');
expect(service.reloadPage).toHaveBeenCalledOnceWith();
});

});
29 changes: 28 additions & 1 deletion lib/core/src/lib/auth/oidc/redirect-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Inject, Injectable, inject } from '@angular/core';
import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthEvent, OAuthService, OAuthStorage, TokenResponse, LoginOptions, OAuthSuccessEvent } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { from, Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, shareReplay, take } from 'rxjs/operators';
import { AuthService } from './auth.service';
import { AUTH_MODULE_CONFIG, AuthModuleConfig } from './auth-config';

Expand Down Expand Up @@ -53,6 +53,21 @@ export class RedirectAuthService extends AuthService {

private authConfig!: AuthConfig | Promise<AuthConfig>;

private readonly AUTH_STORAGE_ITEMS: string[] = [
'access_token',
'access_token_stored_at',
'expires_at',
'granted_scopes',
'id_token',
'id_token_claims_obj',
'id_token_expires_at',
'id_token_stored_at',
'nonce',
'PKCE_verifier',
'refresh_token',
'session_state'
];

constructor(
private oauthService: OAuthService,
private _oauthStorage: OAuthStorage,
Expand All @@ -69,6 +84,13 @@ export class RedirectAuthService extends AuthService {
shareReplay(1)
);

this.oauthService.events.pipe(take(1)).subscribe(() => {
if(this.oauthService.getAccessToken() && !this.authenticated){
this.AUTH_STORAGE_ITEMS.map((item: string) => this._oauthStorage.removeItem(item));
this.reloadPage();
}
});

this.onLogin = this.authenticated$.pipe(
filter((authenticated) => authenticated),
map(() => undefined)
Expand Down Expand Up @@ -223,4 +245,9 @@ export class RedirectAuthService extends AuthService {
updateIDPConfiguration(config: AuthConfig) {
this.oauthService.configure(config);
}

reloadPage() {
window.location.reload();
}

}
18 changes: 6 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@ngx-translate/core": "^14.0.0",
"@storybook/core-server": "8.2.6",
"@storybook/theming": "8.2.6",
"angular-oauth2-oidc": "^13.0.1",
"angular-oauth2-oidc": "^15.0.1",
"angular-oauth2-oidc-jwks": "^17.0.2",
"apollo-angular": "^5.0.2",
"chart.js": "^4.3.0",
Expand Down

0 comments on commit b20107f

Please sign in to comment.