UNPKG

angular-auth-oidc-client

Version:

An OpenID Connect Code Flow with PKCE,Implicit Flow client for Angular

1,207 lines 149 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable, NgZone } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, from, Observable, of, Subject, throwError as observableThrowError, timer } from 'rxjs'; import { catchError, filter, map, race, shareReplay, switchMap, switchMapTo, take, tap } from 'rxjs/operators'; import { OidcDataService } from '../data-services/oidc-data.service'; import { AuthorizationResult } from '../models/authorization-result'; import { AuthorizationState } from '../models/authorization-state.enum'; import { ValidateStateResult } from '../models/validate-state-result.model'; import { ValidationResult } from '../models/validation-result.enum'; import { ConfigurationProvider } from './auth-configuration.provider'; import { StateValidationService } from './oidc-security-state-validation.service'; import { TokenHelperService } from './oidc-token-helper.service'; import { LoggerService } from './oidc.logger.service'; import { OidcSecurityCheckSession } from './oidc.security.check-session'; import { OidcSecurityCommon } from './oidc.security.common'; import { OidcSecuritySilentRenew } from './oidc.security.silent-renew'; import { OidcSecurityUserService } from './oidc.security.user-service'; import { OidcSecurityValidation } from './oidc.security.validation'; import { UriEncoder } from './uri-encoder'; export class OidcSecurityService { /** * @param {?} oidcDataService * @param {?} stateValidationService * @param {?} router * @param {?} oidcSecurityCheckSession * @param {?} oidcSecuritySilentRenew * @param {?} oidcSecurityUserService * @param {?} oidcSecurityCommon * @param {?} oidcSecurityValidation * @param {?} tokenHelperService * @param {?} loggerService * @param {?} zone * @param {?} httpClient * @param {?} configurationProvider */ constructor(oidcDataService, stateValidationService, router, oidcSecurityCheckSession, oidcSecuritySilentRenew, oidcSecurityUserService, oidcSecurityCommon, oidcSecurityValidation, tokenHelperService, loggerService, zone, httpClient, configurationProvider) { this.oidcDataService = oidcDataService; this.stateValidationService = stateValidationService; this.router = router; this.oidcSecurityCheckSession = oidcSecurityCheckSession; this.oidcSecuritySilentRenew = oidcSecuritySilentRenew; this.oidcSecurityUserService = oidcSecurityUserService; this.oidcSecurityCommon = oidcSecurityCommon; this.oidcSecurityValidation = oidcSecurityValidation; this.tokenHelperService = tokenHelperService; this.loggerService = loggerService; this.zone = zone; this.httpClient = httpClient; this.configurationProvider = configurationProvider; this._onModuleSetup = new Subject(); this._onCheckSessionChanged = new Subject(); this._onAuthorizationResult = new Subject(); this.checkSessionChanged = false; this.moduleSetup = false; this._isModuleSetup = new BehaviorSubject(false); this._isAuthorized = new BehaviorSubject(false); this._userData = new BehaviorSubject(''); this.authWellKnownEndpointsLoaded = false; this.runTokenValidationRunning = false; this.onModuleSetup.pipe(take(1)).subscribe((/** * @return {?} */ () => { this.moduleSetup = true; this._isModuleSetup.next(true); })); this._isSetupAndAuthorized = this._isModuleSetup.pipe(filter((/** * @param {?} isModuleSetup * @return {?} */ (isModuleSetup) => isModuleSetup)), switchMap((/** * @return {?} */ () => { if (!this.configurationProvider.openIDConfiguration.silent_renew) { this.loggerService.logDebug(`IsAuthorizedRace: Silent Renew Not Active. Emitting.`); return from([true]); } /** @type {?} */ const race$ = this._isAuthorized.asObservable().pipe(filter((/** * @param {?} isAuthorized * @return {?} */ (isAuthorized) => isAuthorized)), take(1), tap((/** * @return {?} */ () => this.loggerService.logDebug('IsAuthorizedRace: Existing token is still authorized.'))), race(this._onAuthorizationResult.pipe(take(1), tap((/** * @return {?} */ () => this.loggerService.logDebug('IsAuthorizedRace: Silent Renew Refresh Session Complete'))), map((/** * @return {?} */ () => true))), timer(5000).pipe( // backup, if nothing happens after 5 seconds stop waiting and emit tap((/** * @return {?} */ () => { this.resetAuthorizationData(false); this.oidcSecurityCommon.authNonce = ''; this.loggerService.logWarning('IsAuthorizedRace: Timeout reached. Emitting.'); })), map((/** * @return {?} */ () => true))))); this.loggerService.logDebug('Silent Renew is active, check if token in storage is active'); if (this.oidcSecurityCommon.authNonce === '' || this.oidcSecurityCommon.authNonce === undefined) { // login not running, or a second silent renew, user must login first before this will work. this.loggerService.logDebug('Silent Renew or login not running, try to refresh the session'); this.refreshSession(); } return race$; })), tap((/** * @return {?} */ () => this.loggerService.logDebug('IsAuthorizedRace: Completed'))), switchMapTo(this._isAuthorized.asObservable()), tap((/** * @param {?} isAuthorized * @return {?} */ (isAuthorized) => this.loggerService.logDebug(`getIsAuthorized: ${isAuthorized}`))), shareReplay(1)); this._isSetupAndAuthorized .pipe(filter((/** * @return {?} */ () => this.configurationProvider.openIDConfiguration.start_checksession))) .subscribe((/** * @param {?} isSetupAndAuthorized * @return {?} */ isSetupAndAuthorized => { if (isSetupAndAuthorized) { this.oidcSecurityCheckSession.startCheckingSession(this.configurationProvider.openIDConfiguration.client_id); } else { this.oidcSecurityCheckSession.stopCheckingSession(); } })); } /** * @return {?} */ get onModuleSetup() { return this._onModuleSetup.asObservable(); } /** * @return {?} */ get onAuthorizationResult() { return this._onAuthorizationResult.asObservable(); } /** * @return {?} */ get onCheckSessionChanged() { return this._onCheckSessionChanged.asObservable(); } /** * @return {?} */ get onConfigurationChange() { return this.configurationProvider.onConfigurationChange; } /** * @param {?} openIdConfiguration * @param {?} authWellKnownEndpoints * @return {?} */ setupModule(openIdConfiguration, authWellKnownEndpoints) { this.configurationProvider.setup(openIdConfiguration, authWellKnownEndpoints); this.oidcSecurityCheckSession.onCheckSessionChanged.subscribe((/** * @return {?} */ () => { this.loggerService.logDebug('onCheckSessionChanged'); this.checkSessionChanged = true; this._onCheckSessionChanged.next(this.checkSessionChanged); })); /** @type {?} */ const userData = this.oidcSecurityCommon.userData; if (userData) { this.setUserData(userData); } /** @type {?} */ const isAuthorized = this.oidcSecurityCommon.isAuthorized; if (isAuthorized) { this.loggerService.logDebug('IsAuthorized setup module'); this.loggerService.logDebug(this.oidcSecurityCommon.idToken); if (this.oidcSecurityValidation.isTokenExpired(this.oidcSecurityCommon.idToken, this.configurationProvider.openIDConfiguration.silent_renew_offset_in_seconds)) { this.loggerService.logDebug('IsAuthorized setup module; id_token isTokenExpired'); } else { this.loggerService.logDebug('IsAuthorized setup module; id_token is valid'); this.setIsAuthorized(isAuthorized); } this.runTokenValidation(); } this.loggerService.logDebug('STS server: ' + this.configurationProvider.openIDConfiguration.stsServer); this._onModuleSetup.next(); if (this.configurationProvider.openIDConfiguration.silent_renew) { this.oidcSecuritySilentRenew.initRenew(); // Support authorization via DOM events. // Deregister if OidcSecurityService.setupModule is called again by any instance. // We only ever want the latest setup service to be reacting to this event. this.boundSilentRenewEvent = this.silentRenewEventHandler.bind(this); /** @type {?} */ const instanceId = Math.random(); /** @type {?} */ const boundSilentRenewInitEvent = ((/** * @param {?} e * @return {?} */ (e) => { if (e.detail !== instanceId) { window.removeEventListener('oidc-silent-renew-message', this.boundSilentRenewEvent); window.removeEventListener('oidc-silent-renew-init', boundSilentRenewInitEvent); } })).bind(this); window.addEventListener('oidc-silent-renew-init', boundSilentRenewInitEvent, false); window.addEventListener('oidc-silent-renew-message', this.boundSilentRenewEvent, false); window.dispatchEvent(new CustomEvent('oidc-silent-renew-init', { detail: instanceId, })); } } /** * @return {?} */ getUserData() { return this._userData.asObservable(); } /** * @return {?} */ getIsModuleSetup() { return this._isModuleSetup.asObservable(); } /** * @return {?} */ getIsAuthorized() { return this._isSetupAndAuthorized; } /** * @return {?} */ getToken() { if (!this._isAuthorized.getValue()) { return ''; } /** @type {?} */ const token = this.oidcSecurityCommon.getAccessToken(); return decodeURIComponent(token); } /** * @return {?} */ getIdToken() { if (!this._isAuthorized.getValue()) { return ''; } /** @type {?} */ const token = this.oidcSecurityCommon.getIdToken(); return decodeURIComponent(token); } /** * @param {?=} encode * @return {?} */ getPayloadFromIdToken(encode = false) { /** @type {?} */ const token = this.getIdToken(); return this.tokenHelperService.getPayloadFromToken(token, encode); } /** * @param {?} state * @return {?} */ setState(state) { this.oidcSecurityCommon.authStateControl = state; } /** * @return {?} */ getState() { return this.oidcSecurityCommon.authStateControl; } /** * @param {?} params * @return {?} */ setCustomRequestParameters(params) { this.oidcSecurityCommon.customRequestParams = params; } // Code Flow with PCKE or Implicit Flow /** * @param {?=} urlHandler * @return {?} */ authorize(urlHandler) { if (this.configurationProvider.wellKnownEndpoints) { this.authWellKnownEndpointsLoaded = true; } if (!this.authWellKnownEndpointsLoaded) { this.loggerService.logError('Well known endpoints must be loaded before user can login!'); return; } if (!this.oidcSecurityValidation.config_validate_response_type(this.configurationProvider.openIDConfiguration.response_type)) { // invalid response_type return; } this.resetAuthorizationData(false); this.loggerService.logDebug('BEGIN Authorize Code Flow, no auth data'); /** @type {?} */ let state = this.oidcSecurityCommon.authStateControl; if (!state) { state = Date.now() + '' + Math.random() + Math.random(); this.oidcSecurityCommon.authStateControl = state; } /** @type {?} */ const nonce = 'N' + Math.random() + '' + Date.now(); this.oidcSecurityCommon.authNonce = nonce; this.loggerService.logDebug('AuthorizedController created. local state: ' + this.oidcSecurityCommon.authStateControl); /** @type {?} */ let url = ''; // Code Flow if (this.configurationProvider.openIDConfiguration.response_type === 'code') { // code_challenge with "S256" /** @type {?} */ const code_verifier = 'C' + Math.random() + '' + Date.now() + '' + Date.now() + Math.random(); /** @type {?} */ const code_challenge = this.oidcSecurityValidation.generate_code_verifier(code_verifier); this.oidcSecurityCommon.code_verifier = code_verifier; if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl(true, code_challenge, this.configurationProvider.openIDConfiguration.redirect_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || ''); } else { this.loggerService.logError('authWellKnownEndpoints is undefined'); } } else { // Implicit Flow if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl(false, '', this.configurationProvider.openIDConfiguration.redirect_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || ''); } else { this.loggerService.logError('authWellKnownEndpoints is undefined'); } } if (urlHandler) { urlHandler(url); } else { this.redirectTo(url); } } // Code Flow /** * @param {?} urlToCheck * @return {?} */ authorizedCallbackWithCode(urlToCheck) { /** @type {?} */ const urlParts = urlToCheck.split('?'); /** @type {?} */ const params = new HttpParams({ fromString: urlParts[1], }); /** @type {?} */ const code = params.get('code'); /** @type {?} */ const state = params.get('state'); /** @type {?} */ const session_state = params.get('session_state'); if (code && state) { this.requestTokensWithCode(code, state, session_state); } } // Code Flow /** * @param {?} code * @param {?} state * @param {?} session_state * @return {?} */ requestTokensWithCode(code, state, session_state) { this._isModuleSetup .pipe(filter((/** * @param {?} isModuleSetup * @return {?} */ (isModuleSetup) => isModuleSetup)), take(1)) .subscribe((/** * @return {?} */ () => { this.requestTokensWithCodeProcedure(code, state, session_state); })); } // Code Flow with PCKE /** * @param {?} code * @param {?} state * @param {?} session_state * @return {?} */ requestTokensWithCodeProcedure(code, state, session_state) { /** @type {?} */ let tokenRequestUrl = ''; if (this.configurationProvider.wellKnownEndpoints && this.configurationProvider.wellKnownEndpoints.token_endpoint) { tokenRequestUrl = `${this.configurationProvider.wellKnownEndpoints.token_endpoint}`; } if (!this.oidcSecurityValidation.validateStateFromHashCallback(state, this.oidcSecurityCommon.authStateControl)) { this.loggerService.logWarning('authorizedCallback incorrect state'); // ValidationResult.StatesDoNotMatch; return; } /** @type {?} */ let headers = new HttpHeaders(); headers = headers.set('Content-Type', 'application/x-www-form-urlencoded'); /** @type {?} */ let data = `grant_type=authorization_code&client_id=${this.configurationProvider.openIDConfiguration.client_id}` + `&code_verifier=${this.oidcSecurityCommon.code_verifier}&code=${code}&redirect_uri=${this.configurationProvider.openIDConfiguration.redirect_url}`; if (this.oidcSecurityCommon.silentRenewRunning === 'running') { data = `grant_type=authorization_code&client_id=${this.configurationProvider.openIDConfiguration.client_id}` + `&code_verifier=${this.oidcSecurityCommon.code_verifier}&code=${code}&redirect_uri=${this.configurationProvider.openIDConfiguration.silent_renew_url}`; } this.httpClient .post(tokenRequestUrl, data, { headers: headers }) .pipe(map((/** * @param {?} response * @return {?} */ response => { /** @type {?} */ let obj = new Object(); obj = response; obj.state = state; obj.session_state = session_state; this.authorizedCodeFlowCallbackProcedure(obj); })), catchError((/** * @param {?} error * @return {?} */ error => { this.loggerService.logError(error); this.loggerService.logError(`OidcService code request ${this.configurationProvider.openIDConfiguration.stsServer}`); return of(false); }))) .subscribe(); } // Code Flow /** * @private * @param {?} result * @return {?} */ authorizedCodeFlowCallbackProcedure(result) { /** @type {?} */ const silentRenew = this.oidcSecurityCommon.silentRenewRunning; /** @type {?} */ const isRenewProcess = silentRenew === 'running'; this.loggerService.logDebug('BEGIN authorized Code Flow Callback, no auth data'); this.resetAuthorizationData(isRenewProcess); this.authorizedCallbackProcedure(result, isRenewProcess); } // Implicit Flow /** * @private * @param {?=} hash * @return {?} */ authorizedImplicitFlowCallbackProcedure(hash) { /** @type {?} */ const silentRenew = this.oidcSecurityCommon.silentRenewRunning; /** @type {?} */ const isRenewProcess = silentRenew === 'running'; this.loggerService.logDebug('BEGIN authorizedCallback, no auth data'); this.resetAuthorizationData(isRenewProcess); hash = hash || window.location.hash.substr(1); /** @type {?} */ const result = hash.split('&').reduce((/** * @param {?} resultData * @param {?} item * @return {?} */ function (resultData, item) { /** @type {?} */ const parts = item.split('='); resultData[(/** @type {?} */ (parts.shift()))] = parts.join('='); return resultData; }), {}); this.authorizedCallbackProcedure(result, isRenewProcess); } // Implicit Flow /** * @param {?=} hash * @return {?} */ authorizedImplicitFlowCallback(hash) { this._isModuleSetup .pipe(filter((/** * @param {?} isModuleSetup * @return {?} */ (isModuleSetup) => isModuleSetup)), take(1)) .subscribe((/** * @return {?} */ () => { this.authorizedImplicitFlowCallbackProcedure(hash); })); } /** * @private * @param {?} url * @return {?} */ redirectTo(url) { window.location.href = url; } // Implicit Flow /** * @private * @param {?} result * @param {?} isRenewProcess * @return {?} */ authorizedCallbackProcedure(result, isRenewProcess) { this.oidcSecurityCommon.authResult = result; if (!this.configurationProvider.openIDConfiguration.history_cleanup_off && !isRenewProcess) { // reset the history to remove the tokens window.history.replaceState({}, window.document.title, window.location.origin + window.location.pathname); } else { this.loggerService.logDebug('history clean up inactive'); } if (result.error) { if (isRenewProcess) { this.loggerService.logDebug(result); } else { this.loggerService.logWarning(result); } if (((/** @type {?} */ (result.error))) === 'login_required') { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.LoginRequired)); } else { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.SecureTokenServerError)); } this.resetAuthorizationData(false); this.oidcSecurityCommon.authNonce = ''; if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } else { this.loggerService.logDebug(result); this.loggerService.logDebug('authorizedCallback created, begin token validation'); this.getSigningKeys().subscribe((/** * @param {?} jwtKeys * @return {?} */ jwtKeys => { /** @type {?} */ const validationResult = this.getValidatedStateResult(result, jwtKeys); if (validationResult.authResponseIsValid) { this.setAuthorizationData(validationResult.access_token, validationResult.id_token); this.oidcSecurityCommon.silentRenewRunning = ''; if (this.configurationProvider.openIDConfiguration.auto_userinfo) { this.getUserinfo(isRenewProcess, result, validationResult.id_token, validationResult.decoded_id_token).subscribe((/** * @param {?} response * @return {?} */ response => { if (response) { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.authorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.post_login_route]); } } else { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } }), (/** * @param {?} err * @return {?} */ err => { /* Something went wrong while getting signing key */ this.loggerService.logWarning('Failed to retreive user info with error: ' + JSON.stringify(err)); })); } else { if (!isRenewProcess) { // userData is set to the id_token decoded, auto get user data set to false this.oidcSecurityUserService.setUserData(validationResult.decoded_id_token); this.setUserData(this.oidcSecurityUserService.getUserData()); } this.runTokenValidation(); this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.authorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.post_login_route]); } } } else { // something went wrong this.loggerService.logWarning('authorizedCallback, token(s) validation failed, resetting'); this.loggerService.logWarning(window.location.hash); this.resetAuthorizationData(false); this.oidcSecurityCommon.silentRenewRunning = ''; this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, validationResult.state)); if (!this.configurationProvider.openIDConfiguration.trigger_authorization_result_event && !isRenewProcess) { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } }), (/** * @param {?} err * @return {?} */ err => { /* Something went wrong while getting signing key */ this.loggerService.logWarning('Failed to retreive siging key with error: ' + JSON.stringify(err)); this.oidcSecurityCommon.silentRenewRunning = ''; })); } } /** * @param {?=} isRenewProcess * @param {?=} result * @param {?=} id_token * @param {?=} decoded_id_token * @return {?} */ getUserinfo(isRenewProcess = false, result, id_token, decoded_id_token) { result = result ? result : this.oidcSecurityCommon.authResult; id_token = id_token ? id_token : this.oidcSecurityCommon.idToken; decoded_id_token = decoded_id_token ? decoded_id_token : this.tokenHelperService.getPayloadFromToken(id_token, false); return new Observable((/** * @param {?} observer * @return {?} */ observer => { // flow id_token token if (this.configurationProvider.openIDConfiguration.response_type === 'id_token token' || this.configurationProvider.openIDConfiguration.response_type === 'code') { if (isRenewProcess && this._userData.value) { this.oidcSecurityCommon.sessionState = result.session_state; observer.next(true); observer.complete(); } else { this.oidcSecurityUserService.initUserData().subscribe((/** * @return {?} */ () => { this.loggerService.logDebug('authorizedCallback (id_token token || code) flow'); /** @type {?} */ const userData = this.oidcSecurityUserService.getUserData(); if (this.oidcSecurityValidation.validate_userdata_sub_id_token(decoded_id_token.sub, userData.sub)) { this.setUserData(userData); this.loggerService.logDebug(this.oidcSecurityCommon.accessToken); this.loggerService.logDebug(this.oidcSecurityUserService.getUserData()); this.oidcSecurityCommon.sessionState = result.session_state; this.runTokenValidation(); observer.next(true); } else { // something went wrong, userdata sub does not match that from id_token this.loggerService.logWarning('authorizedCallback, User data sub does not match sub in id_token'); this.loggerService.logDebug('authorizedCallback, token(s) validation failed, resetting'); this.resetAuthorizationData(false); observer.next(false); } observer.complete(); })); } } else { // flow id_token this.loggerService.logDebug('authorizedCallback id_token flow'); this.loggerService.logDebug(this.oidcSecurityCommon.accessToken); // userData is set to the id_token decoded. No access_token. this.oidcSecurityUserService.setUserData(decoded_id_token); this.setUserData(this.oidcSecurityUserService.getUserData()); this.oidcSecurityCommon.sessionState = result.session_state; this.runTokenValidation(); observer.next(true); observer.complete(); } })); } /** * @param {?=} urlHandler * @return {?} */ logoff(urlHandler) { // /connect/endsession?id_token_hint=...&post_logout_redirect_uri=https://myapp.com this.loggerService.logDebug('BEGIN Authorize, no auth data'); if (this.configurationProvider.wellKnownEndpoints) { if (this.configurationProvider.wellKnownEndpoints.end_session_endpoint) { /** @type {?} */ const end_session_endpoint = this.configurationProvider.wellKnownEndpoints.end_session_endpoint; /** @type {?} */ const id_token_hint = this.oidcSecurityCommon.idToken; /** @type {?} */ const url = this.createEndSessionUrl(end_session_endpoint, id_token_hint); this.resetAuthorizationData(false); if (this.configurationProvider.openIDConfiguration.start_checksession && this.checkSessionChanged) { this.loggerService.logDebug('only local login cleaned up, server session has changed'); } else if (urlHandler) { urlHandler(url); } else { this.redirectTo(url); } } else { this.resetAuthorizationData(false); this.loggerService.logDebug('only local login cleaned up, no end_session_endpoint'); } } else { this.loggerService.logWarning('authWellKnownEndpoints is undefined'); } } /** * @return {?} */ refreshSession() { if (!this.configurationProvider.openIDConfiguration.silent_renew) { return from([false]); } this.loggerService.logDebug('BEGIN refresh session Authorize'); /** @type {?} */ let state = this.oidcSecurityCommon.authStateControl; if (state === '' || state === null) { state = Date.now() + '' + Math.random() + Math.random(); this.oidcSecurityCommon.authStateControl = state; } /** @type {?} */ const nonce = 'N' + Math.random() + '' + Date.now(); this.oidcSecurityCommon.authNonce = nonce; this.loggerService.logDebug('RefreshSession created. adding myautostate: ' + this.oidcSecurityCommon.authStateControl); /** @type {?} */ let url = ''; // Code Flow if (this.configurationProvider.openIDConfiguration.response_type === 'code') { // code_challenge with "S256" /** @type {?} */ const code_verifier = 'C' + Math.random() + '' + Date.now() + '' + Date.now() + Math.random(); /** @type {?} */ const code_challenge = this.oidcSecurityValidation.generate_code_verifier(code_verifier); this.oidcSecurityCommon.code_verifier = code_verifier; if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl(true, code_challenge, this.configurationProvider.openIDConfiguration.silent_renew_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || '', 'none'); } else { this.loggerService.logWarning('authWellKnownEndpoints is undefined'); } } else { if (this.configurationProvider.wellKnownEndpoints) { url = this.createAuthorizeUrl(false, '', this.configurationProvider.openIDConfiguration.silent_renew_url, nonce, state, this.configurationProvider.wellKnownEndpoints.authorization_endpoint || '', 'none'); } else { this.loggerService.logWarning('authWellKnownEndpoints is undefined'); } } this.oidcSecurityCommon.silentRenewRunning = 'running'; return this.oidcSecuritySilentRenew.startRenew(url); } /** * @param {?} error * @return {?} */ handleError(error) { this.loggerService.logError(error); if (error.status === 403 || error.status === '403') { if (this.configurationProvider.openIDConfiguration.trigger_authorization_result_event) { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.NotSet)); } else { this.router.navigate([this.configurationProvider.openIDConfiguration.forbidden_route]); } } else if (error.status === 401 || error.status === '401') { /** @type {?} */ const silentRenew = this.oidcSecurityCommon.silentRenewRunning; this.resetAuthorizationData(!!silentRenew); if (this.configurationProvider.openIDConfiguration.trigger_authorization_result_event) { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.NotSet)); } else { this.router.navigate([this.configurationProvider.openIDConfiguration.unauthorized_route]); } } } /** * @return {?} */ startCheckingSilentRenew() { this.runTokenValidation(); } /** * @return {?} */ stopCheckingSilentRenew() { if (this._scheduledHeartBeat) { clearTimeout(this._scheduledHeartBeat); this._scheduledHeartBeat = null; this.runTokenValidationRunning = false; } } /** * @param {?} isRenewProcess * @return {?} */ resetAuthorizationData(isRenewProcess) { if (!isRenewProcess) { if (this.configurationProvider.openIDConfiguration.auto_userinfo) { // Clear user data. Fixes #97. this.setUserData(''); } this.oidcSecurityCommon.resetStorageData(isRenewProcess); this.checkSessionChanged = false; this.setIsAuthorized(false); } } /** * @return {?} */ getEndSessionUrl() { if (this.configurationProvider.wellKnownEndpoints) { if (this.configurationProvider.wellKnownEndpoints.end_session_endpoint) { /** @type {?} */ const end_session_endpoint = this.configurationProvider.wellKnownEndpoints.end_session_endpoint; /** @type {?} */ const id_token_hint = this.oidcSecurityCommon.idToken; return this.createEndSessionUrl(end_session_endpoint, id_token_hint); } } } /** * @private * @param {?} result * @param {?} jwtKeys * @return {?} */ getValidatedStateResult(result, jwtKeys) { if (result.error) { return new ValidateStateResult('', '', false, {}); } return this.stateValidationService.validateState(result, jwtKeys); } /** * @private * @param {?} userData * @return {?} */ setUserData(userData) { this.oidcSecurityCommon.userData = userData; this._userData.next(userData); } /** * @private * @param {?} isAuthorized * @return {?} */ setIsAuthorized(isAuthorized) { this._isAuthorized.next(isAuthorized); } /** * @private * @param {?} access_token * @param {?} id_token * @return {?} */ setAuthorizationData(access_token, id_token) { if (this.oidcSecurityCommon.accessToken !== '') { this.oidcSecurityCommon.accessToken = ''; } this.loggerService.logDebug(access_token); this.loggerService.logDebug(id_token); this.loggerService.logDebug('storing to storage, getting the roles'); this.oidcSecurityCommon.accessToken = access_token; this.oidcSecurityCommon.idToken = id_token; this.setIsAuthorized(true); this.oidcSecurityCommon.isAuthorized = true; } /** * @private * @param {?} isCodeFlow * @param {?} code_challenge * @param {?} redirect_url * @param {?} nonce * @param {?} state * @param {?} authorization_endpoint * @param {?=} prompt * @return {?} */ createAuthorizeUrl(isCodeFlow, code_challenge, redirect_url, nonce, state, authorization_endpoint, prompt) { /** @type {?} */ const urlParts = authorization_endpoint.split('?'); /** @type {?} */ const authorizationUrl = urlParts[0]; /** @type {?} */ let params = new HttpParams({ fromString: urlParts[1], encoder: new UriEncoder(), }); params = params.set('client_id', this.configurationProvider.openIDConfiguration.client_id); params = params.append('redirect_uri', redirect_url); params = params.append('response_type', this.configurationProvider.openIDConfiguration.response_type); params = params.append('scope', this.configurationProvider.openIDConfiguration.scope); params = params.append('nonce', nonce); params = params.append('state', state); if (isCodeFlow) { params = params.append('code_challenge', code_challenge); params = params.append('code_challenge_method', 'S256'); } if (prompt) { params = params.append('prompt', prompt); } if (this.configurationProvider.openIDConfiguration.hd_param) { params = params.append('hd', this.configurationProvider.openIDConfiguration.hd_param); } /** @type {?} */ const customParams = Object.assign({}, this.oidcSecurityCommon.customRequestParams); Object.keys(customParams).forEach((/** * @param {?} key * @return {?} */ key => { params = params.append(key, customParams[key].toString()); })); return `${authorizationUrl}?${params}`; } /** * @private * @param {?} end_session_endpoint * @param {?} id_token_hint * @return {?} */ createEndSessionUrl(end_session_endpoint, id_token_hint) { /** @type {?} */ const urlParts = end_session_endpoint.split('?'); /** @type {?} */ const authorizationEndsessionUrl = urlParts[0]; /** @type {?} */ let params = new HttpParams({ fromString: urlParts[1], encoder: new UriEncoder(), }); params = params.set('id_token_hint', id_token_hint); params = params.append('post_logout_redirect_uri', this.configurationProvider.openIDConfiguration.post_logout_redirect_uri); return `${authorizationEndsessionUrl}?${params}`; } /** * @private * @return {?} */ getSigningKeys() { if (this.configurationProvider.wellKnownEndpoints) { this.loggerService.logDebug('jwks_uri: ' + this.configurationProvider.wellKnownEndpoints.jwks_uri); return this.oidcDataService .get(this.configurationProvider.wellKnownEndpoints.jwks_uri || '') .pipe(catchError(this.handleErrorGetSigningKeys)); } else { this.loggerService.logWarning('getSigningKeys: authWellKnownEndpoints is undefined'); } return this.oidcDataService.get('undefined').pipe(catchError(this.handleErrorGetSigningKeys)); } /** * @private * @param {?} error * @return {?} */ handleErrorGetSigningKeys(error) { /** @type {?} */ let errMsg; if (error instanceof Response) { /** @type {?} */ const body = error.json() || {}; /** @type {?} */ const err = JSON.stringify(body); errMsg = `${error.status} - ${error.statusText || ''} ${err}`; } else { errMsg = error.message ? error.message : error.toString(); } console.error(errMsg); return observableThrowError(errMsg); } /** * @private * @return {?} */ runTokenValidation() { if (this.runTokenValidationRunning || !this.configurationProvider.openIDConfiguration.silent_renew) { return; } this.runTokenValidationRunning = true; this.loggerService.logDebug('runTokenValidation silent-renew running'); /** * First time: delay 10 seconds to call silentRenewHeartBeatCheck * Afterwards: Run this check in a 5 second interval only AFTER the previous operation ends. * @type {?} */ const silentRenewHeartBeatCheck = (/** * @return {?} */ () => { this.loggerService.logDebug('silentRenewHeartBeatCheck\r\n' + `\tsilentRenewRunning: ${this.oidcSecurityCommon.silentRenewRunning === 'running'}\r\n` + `\tidToken: ${!!this.getIdToken()}\r\n` + `\t_userData.value: ${!!this._userData.value}`); if (this._userData.value && this.oidcSecurityCommon.silentRenewRunning !== 'running' && this.getIdToken()) { if (this.oidcSecurityValidation.isTokenExpired(this.oidcSecurityCommon.idToken, this.configurationProvider.openIDConfiguration.silent_renew_offset_in_seconds)) { this.loggerService.logDebug('IsAuthorized: id_token isTokenExpired, start silent renew if active'); if (this.configurationProvider.openIDConfiguration.silent_renew) { this.refreshSession().subscribe((/** * @return {?} */ () => { this._scheduledHeartBeat = setTimeout(silentRenewHeartBeatCheck, 3000); }), (/** * @param {?} err * @return {?} */ (err) => { this.loggerService.logError('Error: ' + err); this._scheduledHeartBeat = setTimeout(silentRenewHeartBeatCheck, 3000); })); /* In this situation, we schedule a heatbeat check only when silentRenew is finished. We don't want to schedule another check so we have to return here */ return; } else { this.resetAuthorizationData(false); } } } /* Delay 3 seconds and do the next check */ this._scheduledHeartBeat = setTimeout(silentRenewHeartBeatCheck, 3000); }); this.zone.runOutsideAngular((/** * @return {?} */ () => { /* Initial heartbeat check */ this._scheduledHeartBeat = setTimeout(silentRenewHeartBeatCheck, 10000); })); } /** * @private * @param {?} e * @return {?} */ silentRenewEventHandler(e) { this.loggerService.logDebug('silentRenewEventHandler'); if (this.configurationProvider.openIDConfiguration.response_type === 'code') { /** @type {?} */ const urlParts = e.detail.toString().split('?'); /** @type {?} */ const params = new HttpParams({ fromString: urlParts[1], }); /** @type {?} */ const code = params.get('code'); /** @type {?} */ const state = params.get('state'); /** @type {?} */ const session_state = params.get('session_state'); /** @type {?} */ const error = params.get('error'); if (code && state) { this.requestTokensWithCodeProcedure(code, state, session_state); } if (error) { this._onAuthorizationResult.next(new AuthorizationResult(AuthorizationState.unauthorized, ValidationResult.LoginRequired)); this.resetAuthorizationData(false); this.oidcSecurityCommon.authNonce = ''; this.loggerService.logDebug(e.detail.toString()); } } else { // ImplicitFlow this.authorizedImplicitFlowCallback(e.detail); } } } OidcSecurityService.decorators = [ { type: Injectable } ]; /** @nocollapse */ OidcSecurityService.ctorParameters = () => [ { type: OidcDataService }, { type: StateValidationService }, { type: Router }, { type: OidcSecurityCheckSession }, { type: OidcSecuritySilentRenew }, { type: OidcSecurityUserService }, { type: OidcSecurityCommon }, { type: OidcSecurityValidation }, { type: TokenHelperService }, { type: LoggerService }, { type: NgZone }, { type: HttpClient }, { type: ConfigurationProvider } ]; if (false) { /** * @type {?} * @private */ OidcSecurityService.prototype._onModuleSetup; /** * @type {?} * @private */ OidcSecurityService.prototype._onCheckSessionChanged; /** * @type {?} * @private */ OidcSecurityService.prototype._onAuthorizationResult; /** @type {?} */ OidcSecurityService.prototype.checkSessionChanged; /** @type {?} */ OidcSecurityService.prototype.moduleSetup; /** * @type {?} * @private */ OidcSecurityService.prototype._isModuleSetup; /** * @type {?} * @private */ OidcSecurityService.prototype._isAuthorized; /** * @type {?} * @private */ OidcSecurityService.prototype._isSetupAndAuthorized; /** * @type {?} * @private */ OidcSecurityService.prototype._userData; /** * @type {?} * @private */ OidcSecurityService.prototype.authWellKnownEndpointsLoaded; /** * @type {?} * @private */ OidcSecurityService.prototype.runTokenValidationRunning; /** * @type {?} * @private */ OidcSecurityService.prototype._scheduledHeartBeat; /** * @type {?} * @private */ OidcSecurityService.prototype.boundSilentRenewEvent; /** * @type {?} * @private */ OidcSecurityService.prototype.oidcDataService; /** * @type {?} * @private */ OidcSecurityService.prototype.stateValidationService; /** * @type {?} * @private */ OidcSecurityService.prototype.router; /** * @type {?} * @private */ OidcSecurityService.prototype.oidcSecurityCheckSession; /** * @type {?} * @private */ OidcSecurityService.prototype.oidcSe