diff --git a/projects/lib/src/auth.config.ts b/projects/lib/src/auth.config.ts index a2235c3c..3760e2b5 100644 --- a/projects/lib/src/auth.config.ts +++ b/projects/lib/src/auth.config.ts @@ -64,6 +64,11 @@ export class AuthConfig { */ public tokenEndpoint?: string = null; + /** + * Url of the revocation endpoint as defined by OpenId Connect and OAuth 2. + */ + public revocationEndpoint?: string = null; + /** * Names of known parameters sent out in the TokenResponse. https://tools.ietf.org/html/rfc6749#section-5.1 */ diff --git a/projects/lib/src/events.ts b/projects/lib/src/events.ts index 7349b92c..a19bd8fa 100644 --- a/projects/lib/src/events.ts +++ b/projects/lib/src/events.ts @@ -21,7 +21,8 @@ export type EventType = | 'session_terminated' | 'logout' | 'popup_closed' - | 'popup_blocked'; + | 'popup_blocked' + | 'token_revoke_error'; export abstract class OAuthEvent { constructor(readonly type: EventType) {} diff --git a/projects/lib/src/oauth-service.ts b/projects/lib/src/oauth-service.ts index d2a17ebe..f3139b0d 100644 --- a/projects/lib/src/oauth-service.ts +++ b/projects/lib/src/oauth-service.ts @@ -523,6 +523,7 @@ export class OAuthService extends AuthConfig implements OnDestroy { this.discoveryDocumentLoaded = true; this.discoveryDocumentLoadedSubject.next(doc); + this.revocationEndpoint = doc.revocation_endpoint; if (this.sessionChecksEnabled) { this.restartSessionChecksIfStillLoggedIn(); @@ -625,6 +626,14 @@ export class OAuthService extends AuthConfig implements OnDestroy { ); } + errors = this.validateUrlFromDiscoveryDocument(doc.revocation_endpoint); + if (errors.length > 0) { + this.logger.error( + 'error validating revocation_endpoint in discovery document', + errors + ); + } + errors = this.validateUrlFromDiscoveryDocument(doc.userinfo_endpoint); if (errors.length > 0) { this.logger.error( @@ -804,7 +813,8 @@ export class OAuthService extends AuthConfig implements OnDestroy { this.storeAccessTokenResponse( tokenResponse.access_token, tokenResponse.refresh_token, - tokenResponse.expires_in, + tokenResponse.expires_in || + this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse) ); @@ -890,7 +900,8 @@ export class OAuthService extends AuthConfig implements OnDestroy { this.storeAccessTokenResponse( tokenResponse.access_token, tokenResponse.refresh_token, - tokenResponse.expires_in, + tokenResponse.expires_in || + this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse) ); @@ -1729,7 +1740,8 @@ export class OAuthService extends AuthConfig implements OnDestroy { this.storeAccessTokenResponse( tokenResponse.access_token, tokenResponse.refresh_token, - tokenResponse.expires_in, + tokenResponse.expires_in || + this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse) ); @@ -2538,4 +2550,64 @@ export class OAuthService extends AuthConfig implements OnDestroy { }); return foundParameters; } + + /** + * Revokes the auth token to secure the vulnarability + * of the token issued allowing the authorization server to clean + * up any security credentials associated with the authorization + */ + public revokeTokenAndLogout(): Promise { + let revoke_endpoint = this.revocationEndpoint; + let current_access_token = this.getAccessToken(); + let params = new HttpParams() + .set('token', current_access_token) + .set('token_type_hint', 'access_token'); + + let headers = new HttpHeaders().set( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + + if (this.useHttpBasicAuth) { + const header = btoa(`${this.clientId}:${this.dummyClientSecret}`); + headers = headers.set('Authorization', 'Basic ' + header); + } + + if (!this.useHttpBasicAuth) { + params = params.set('client_id', this.clientId); + } + + if (!this.useHttpBasicAuth && this.dummyClientSecret) { + params = params.set('client_secret', this.dummyClientSecret); + } + + if (this.customQueryParams) { + for (const key of Object.getOwnPropertyNames(this.customQueryParams)) { + params = params.set(key, this.customQueryParams[key]); + } + } + + return new Promise((resolve, reject) => { + if (current_access_token) { + this.http + .post(revoke_endpoint, params, { headers }) + .subscribe( + res => { + this.logOut(); + resolve(res); + this.logger.info('Token successfully revoked'); + }, + err => { + this.logger.error('Error revoking token', err); + this.eventsSubject.next( + new OAuthErrorEvent('token_revoke_error', err) + ); + reject(err); + } + ); + } else { + this.logger.warn('User not logged in to revoke token.'); + } + }); + } } diff --git a/projects/lib/src/types.ts b/projects/lib/src/types.ts index 249d9ead..9e19dc41 100644 --- a/projects/lib/src/types.ts +++ b/projects/lib/src/types.ts @@ -187,4 +187,5 @@ export interface OidcDiscoveryDoc { claims_parameter_supported: boolean; service_documentation: string; ui_locales_supported: string[]; + revocation_endpoint: string; }