angular-auth-oidc-client
Version:
An OpenID Connect Code Flow with PKCE,Implicit Flow client for Angular
1,427 lines (1,409 loc) • 127 kB
JavaScript
import { isPlatformBrowser } from '@angular/common';
import { hextob64u, KEYUTIL, KJUR } from 'jsrsasign-reduced';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Router } from '@angular/router';
import { Subject, from, Observable, of, ReplaySubject, BehaviorSubject, throwError, timer } from 'rxjs';
import { take, catchError, switchMap, map, filter, race, shareReplay, switchMapTo, tap } from 'rxjs/operators';
import { Injectable, Inject, PLATFORM_ID, NgZone, NgModule, defineInjectable, inject } from '@angular/core';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class AuthorizationResult {
/**
* @param {?} authorizationState
* @param {?} validationResult
*/
constructor(authorizationState, validationResult) {
this.authorizationState = authorizationState;
this.validationResult = validationResult;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @enum {string} */
const AuthorizationState = {
authorized: 'authorized',
forbidden: 'forbidden',
unauthorized: 'unauthorized',
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class JwtKeys {
constructor() {
this.keys = [];
}
}
class JwtKey {
constructor() {
this.kty = '';
this.use = '';
this.kid = '';
this.x5t = '';
this.e = '';
this.n = '';
this.x5c = [];
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @enum {string} */
const ValidationResult = {
NotSet: 'NotSet',
StatesDoNotMatch: 'StatesDoNotMatch',
SignatureFailed: 'SignatureFailed',
IncorrectNonce: 'IncorrectNonce',
RequiredPropertyMissing: 'RequiredPropertyMissing',
MaxOffsetExpired: 'MaxOffsetExpired',
IssDoesNotMatchIssuer: 'IssDoesNotMatchIssuer',
NoAuthWellKnownEndPoints: 'NoAuthWellKnownEndPoints',
IncorrectAud: 'IncorrectAud',
TokenExpired: 'TokenExpired',
IncorrectAtHash: 'IncorrectAtHash',
Ok: 'Ok',
LoginRequired: 'LoginRequired',
SecureTokenServerError: 'SecureTokenServerError',
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class ValidateStateResult {
/**
* @param {?=} access_token
* @param {?=} id_token
* @param {?=} authResponseIsValid
* @param {?=} decoded_id_token
* @param {?=} state
*/
constructor(access_token = '', id_token = '', authResponseIsValid = false, decoded_id_token = {}, state = ValidationResult.NotSet) {
this.access_token = access_token;
this.id_token = id_token;
this.authResponseIsValid = authResponseIsValid;
this.decoded_id_token = decoded_id_token;
this.state = state;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class OidcDataService {
/**
* @param {?} httpClient
*/
constructor(httpClient) {
this.httpClient = httpClient;
}
/**
* @template T
* @param {?} url
* @return {?}
*/
getWellknownEndpoints(url) {
/** @type {?} */
let headers = new HttpHeaders();
headers = headers.set('Accept', 'application/json');
return this.httpClient.get(url, {
headers: headers,
});
}
/**
* @template T
* @param {?} url
* @param {?} token
* @return {?}
*/
getIdentityUserData(url, token) {
/** @type {?} */
let headers = new HttpHeaders();
headers = headers.set('Accept', 'application/json');
headers = headers.set('Authorization', 'Bearer ' + decodeURIComponent(token));
return this.httpClient.get(url, {
headers: headers,
});
}
/**
* @template T
* @param {?} url
* @return {?}
*/
get(url) {
/** @type {?} */
let headers = new HttpHeaders();
headers = headers.set('Accept', 'application/json');
return this.httpClient.get(url, {
headers: headers,
});
}
}
OidcDataService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
OidcDataService.ctorParameters = () => [
{ type: HttpClient }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class PlatformProvider {
/**
* @param {?} platformId
*/
constructor(platformId) {
this.platformId = platformId;
}
/**
* @return {?}
*/
get isBrowser() {
return isPlatformBrowser(this.platformId);
}
}
PlatformProvider.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
PlatformProvider.ctorParameters = () => [
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
];
/** @nocollapse */ PlatformProvider.ngInjectableDef = defineInjectable({ factory: function PlatformProvider_Factory() { return new PlatformProvider(inject(PLATFORM_ID)); }, token: PlatformProvider, providedIn: "root" });
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class ConfigurationProvider {
/**
* @param {?} platformProvider
*/
constructor(platformProvider) {
this.platformProvider = platformProvider;
this.DEFAULT_CONFIG = {
stsServer: 'https://please_set',
redirect_url: 'https://please_set',
client_id: 'please_set',
response_type: 'code',
scope: 'openid email profile',
hd_param: '',
post_logout_redirect_uri: 'https://please_set',
start_checksession: false,
silent_renew: false,
silent_renew_url: 'https://please_set',
silent_renew_offset_in_seconds: 0,
use_refresh_token: false,
post_login_route: '/',
forbidden_route: '/forbidden',
unauthorized_route: '/unauthorized',
auto_userinfo: true,
auto_clean_state_after_authentication: true,
trigger_authorization_result_event: false,
log_console_warning_active: true,
log_console_debug_active: false,
iss_validation_off: false,
history_cleanup_off: false,
max_id_token_iat_offset_allowed_in_seconds: 3,
isauthorizedrace_timeout_in_seconds: 5,
disable_iat_offset_validation: false,
storage: typeof Storage !== 'undefined' ? sessionStorage : null,
};
this.INITIAL_AUTHWELLKNOWN = {
issuer: '',
jwks_uri: '',
authorization_endpoint: '',
token_endpoint: '',
userinfo_endpoint: '',
end_session_endpoint: '',
check_session_iframe: '',
revocation_endpoint: '',
introspection_endpoint: '',
};
this.mergedOpenIdConfiguration = this.DEFAULT_CONFIG;
this.authWellKnownEndpoints = this.INITIAL_AUTHWELLKNOWN;
this.onConfigurationChangeInternal = new Subject();
}
/**
* @return {?}
*/
get openIDConfiguration() {
return this.mergedOpenIdConfiguration;
}
/**
* @return {?}
*/
get wellKnownEndpoints() {
return this.authWellKnownEndpoints;
}
/**
* @return {?}
*/
get onConfigurationChange() {
return this.onConfigurationChangeInternal.asObservable();
}
/**
* @param {?} passedOpenIfConfiguration
* @param {?} passedAuthWellKnownEndpoints
* @return {?}
*/
setup(passedOpenIfConfiguration, passedAuthWellKnownEndpoints) {
this.mergedOpenIdConfiguration = Object.assign({}, this.mergedOpenIdConfiguration, passedOpenIfConfiguration);
this.setSpecialCases(this.mergedOpenIdConfiguration);
this.authWellKnownEndpoints = Object.assign({}, passedAuthWellKnownEndpoints);
this.onConfigurationChangeInternal.next(Object.assign({}, this.mergedOpenIdConfiguration));
}
/**
* @private
* @param {?} currentConfig
* @return {?}
*/
setSpecialCases(currentConfig) {
if (!this.platformProvider.isBrowser) {
currentConfig.start_checksession = false;
currentConfig.silent_renew = false;
currentConfig.use_refresh_token = false;
}
}
}
ConfigurationProvider.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
ConfigurationProvider.ctorParameters = () => [
{ type: PlatformProvider }
];
/** @nocollapse */ ConfigurationProvider.ngInjectableDef = defineInjectable({ factory: function ConfigurationProvider_Factory() { return new ConfigurationProvider(inject(PlatformProvider)); }, token: ConfigurationProvider, providedIn: "root" });
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class LoggerService {
/**
* @param {?} configurationProvider
*/
constructor(configurationProvider) {
this.configurationProvider = configurationProvider;
}
/**
* @param {?} message
* @param {...?} args
* @return {?}
*/
logError(message, ...args) {
console.error(message, ...args);
}
/**
* @param {?} message
* @return {?}
*/
logWarning(message) {
if (this.configurationProvider.openIDConfiguration.log_console_warning_active) {
console.warn(message);
}
}
/**
* @param {?} message
* @return {?}
*/
logDebug(message) {
if (this.configurationProvider.openIDConfiguration.log_console_debug_active) {
console.log(message);
}
}
}
LoggerService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
LoggerService.ctorParameters = () => [
{ type: ConfigurationProvider }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class IFrameService {
/**
* @param {?} loggerService
*/
constructor(loggerService) {
this.loggerService = loggerService;
}
/**
* @param {?} identifier
* @return {?}
*/
getExistingIFrame(identifier) {
/** @type {?} */
const iFrameOnParent = this.getIFrameFromParentWindow(identifier);
if (this.isIFrameElement(iFrameOnParent)) {
return iFrameOnParent;
}
/** @type {?} */
const iFrameOnSelf = this.getIFrameFromWindow(identifier);
if (this.isIFrameElement(iFrameOnSelf)) {
return iFrameOnSelf;
}
return null;
}
/**
* @param {?} identifier
* @return {?}
*/
addIFrameToWindowBody(identifier) {
/** @type {?} */
const sessionIframe = window.document.createElement('iframe');
sessionIframe.id = identifier;
this.loggerService.logDebug(sessionIframe);
sessionIframe.style.display = 'none';
window.document.body.appendChild(sessionIframe);
return sessionIframe;
}
/**
* @private
* @param {?} identifier
* @return {?}
*/
getIFrameFromParentWindow(identifier) {
try {
/** @type {?} */
const iFrameElement = window.parent.document.getElementById(identifier);
if (this.isIFrameElement(iFrameElement)) {
return iFrameElement;
}
return null;
}
catch (e) {
return null;
}
}
/**
* @private
* @param {?} identifier
* @return {?}
*/
getIFrameFromWindow(identifier) {
/** @type {?} */
const iFrameElement = window.document.getElementById(identifier);
if (this.isIFrameElement(iFrameElement)) {
return iFrameElement;
}
return null;
}
/**
* @private
* @param {?} element
* @return {?}
*/
isIFrameElement(element) {
return !!element && element instanceof HTMLIFrameElement;
}
}
IFrameService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
IFrameService.ctorParameters = () => [
{ type: LoggerService }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class EqualityHelperService {
/**
* @param {?} value1
* @param {?} value2
* @return {?}
*/
areEqual(value1, value2) {
if (!value1 || !value2) {
return false;
}
if (this.bothValuesAreArrays(value1, value2)) {
return this.arraysEqual((/** @type {?} */ (value1)), (/** @type {?} */ (value2)));
}
if (this.bothValuesAreStrings(value1, value2)) {
return value1 === value2;
}
if (this.bothValuesAreObjects(value1, value2)) {
return JSON.stringify(value1).toLowerCase() === JSON.stringify(value2).toLowerCase();
}
if (this.oneValueIsStringAndTheOtherIsArray(value1, value2)) {
if (Array.isArray(value1) && this.valueIsString(value2)) {
return value1[0] === value2;
}
if (Array.isArray(value2) && this.valueIsString(value1)) {
return value2[0] === value1;
}
}
}
/**
* @private
* @param {?} value1
* @param {?} value2
* @return {?}
*/
oneValueIsStringAndTheOtherIsArray(value1, value2) {
return (Array.isArray(value1) && this.valueIsString(value2)) || (Array.isArray(value2) && this.valueIsString(value1));
}
/**
* @private
* @param {?} value1
* @param {?} value2
* @return {?}
*/
bothValuesAreObjects(value1, value2) {
return this.valueIsObject(value1) && this.valueIsObject(value2);
}
/**
* @private
* @param {?} value1
* @param {?} value2
* @return {?}
*/
bothValuesAreStrings(value1, value2) {
return this.valueIsString(value1) && this.valueIsString(value2);
}
/**
* @private
* @param {?} value1
* @param {?} value2
* @return {?}
*/
bothValuesAreArrays(value1, value2) {
return Array.isArray(value1) && Array.isArray(value2);
}
/**
* @private
* @param {?} value
* @return {?}
*/
valueIsString(value) {
return typeof value === 'string' || value instanceof String;
}
/**
* @private
* @param {?} value
* @return {?}
*/
valueIsObject(value) {
return typeof value === 'object';
}
/**
* @private
* @param {?} arr1
* @param {?} arr2
* @return {?}
*/
arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = arr1.length; i--;) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
}
EqualityHelperService.decorators = [
{ type: Injectable }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class TokenHelperService {
/**
* @param {?} loggerService
*/
constructor(loggerService) {
this.loggerService = loggerService;
this.PARTS_OF_TOKEN = 3;
}
/**
* @param {?} dataIdToken
* @return {?}
*/
getTokenExpirationDate(dataIdToken) {
if (!dataIdToken.hasOwnProperty('exp')) {
return new Date();
}
/** @type {?} */
const date = new Date(0);
date.setUTCSeconds(dataIdToken.exp);
return date;
}
/**
* @param {?} token
* @param {?} encoded
* @return {?}
*/
getHeaderFromToken(token, encoded) {
if (!this.tokenIsValid(token)) {
return {};
}
return this.getPartOfToken(token, 0, encoded);
}
/**
* @param {?} token
* @param {?} encoded
* @return {?}
*/
getPayloadFromToken(token, encoded) {
if (!this.tokenIsValid(token)) {
return {};
}
return this.getPartOfToken(token, 1, encoded);
}
/**
* @param {?} token
* @param {?} encoded
* @return {?}
*/
getSignatureFromToken(token, encoded) {
if (!this.tokenIsValid(token)) {
return {};
}
return this.getPartOfToken(token, 2, encoded);
}
/**
* @private
* @param {?} token
* @param {?} index
* @param {?} encoded
* @return {?}
*/
getPartOfToken(token, index, encoded) {
/** @type {?} */
const partOfToken = this.extractPartOfToken(token, index);
if (encoded) {
return partOfToken;
}
/** @type {?} */
const result = this.urlBase64Decode(partOfToken);
return JSON.parse(result);
}
/**
* @private
* @param {?} str
* @return {?}
*/
urlBase64Decode(str) {
/** @type {?} */
let output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw Error('Illegal base64url string!');
}
/** @type {?} */
const decoded = typeof window !== 'undefined' ? window.atob(output) : new Buffer(output, 'base64').toString('binary');
try {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(decoded.split('')
.map((/**
* @param {?} c
* @return {?}
*/
(c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)))
.join(''));
}
catch (err) {
return decoded;
}
}
/**
* @private
* @param {?} token
* @return {?}
*/
tokenIsValid(token) {
if (!token) {
this.loggerService.logError(`token '${token}' is not valid --> token falsy`);
return false;
}
if (!((/** @type {?} */ (token))).includes('.')) {
this.loggerService.logError(`token '${token}' is not valid --> no dots included`);
return false;
}
/** @type {?} */
const parts = token.split('.');
if (parts.length !== this.PARTS_OF_TOKEN) {
this.loggerService.logError(`token '${token}' is not valid --> token has to have exactly ${this.PARTS_OF_TOKEN} dots`);
return false;
}
return true;
}
/**
* @private
* @param {?} token
* @param {?} index
* @return {?}
*/
extractPartOfToken(token, index) {
return token.split('.')[index];
}
}
TokenHelperService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
TokenHelperService.ctorParameters = () => [
{ type: LoggerService }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Implement this class-interface to create a custom storage.
* @abstract
*/
class OidcSecurityStorage {
}
OidcSecurityStorage.decorators = [
{ type: Injectable }
];
class BrowserStorage {
/**
* @param {?} configProvider
*/
constructor(configProvider) {
this.configProvider = configProvider;
this.hasStorage = typeof Storage !== 'undefined';
}
/**
* @param {?} key
* @return {?}
*/
read(key) {
if (this.hasStorage) {
return JSON.parse(this.configProvider.openIDConfiguration.storage.getItem(key + '_' + this.configProvider.openIDConfiguration.client_id));
}
return;
}
/**
* @param {?} key
* @param {?} value
* @return {?}
*/
write(key, value) {
if (this.hasStorage) {
value = value === undefined ? null : value;
this.configProvider.openIDConfiguration.storage.setItem(key + '_' + this.configProvider.openIDConfiguration.client_id, JSON.stringify(value));
}
}
}
BrowserStorage.decorators = [
{ type: Injectable }
];
/** @nocollapse */
BrowserStorage.ctorParameters = () => [
{ type: ConfigurationProvider }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class OidcSecurityCommon {
/**
* @param {?} oidcSecurityStorage
*/
constructor(oidcSecurityStorage) {
this.oidcSecurityStorage = oidcSecurityStorage;
this.storage_auth_result = 'authorizationResult';
this.storage_access_token = 'authorizationData';
this.storage_id_token = 'authorizationDataIdToken';
this.storage_is_authorized = '_isAuthorized';
this.storage_user_data = 'userData';
this.storage_auth_nonce = 'authNonce';
this.storage_code_verifier = 'code_verifier';
this.storage_auth_state_control = 'authStateControl';
this.storage_session_state = 'session_state';
this.storage_silent_renew_running = 'storage_silent_renew_running';
this.storage_custom_request_params = 'storage_custom_request_params';
}
/**
* @return {?}
*/
get authResult() {
return this.retrieve(this.storage_auth_result);
}
/**
* @param {?} value
* @return {?}
*/
set authResult(value) {
this.store(this.storage_auth_result, value);
}
/**
* @return {?}
*/
get accessToken() {
return this.retrieve(this.storage_access_token) || '';
}
/**
* @param {?} value
* @return {?}
*/
set accessToken(value) {
this.store(this.storage_access_token, value);
}
/**
* @return {?}
*/
get idToken() {
return this.retrieve(this.storage_id_token) || '';
}
/**
* @param {?} value
* @return {?}
*/
set idToken(value) {
this.store(this.storage_id_token, value);
}
/**
* @return {?}
*/
get isAuthorized() {
return this.retrieve(this.storage_is_authorized);
}
/**
* @param {?} value
* @return {?}
*/
set isAuthorized(value) {
this.store(this.storage_is_authorized, value);
}
/**
* @return {?}
*/
get userData() {
return this.retrieve(this.storage_user_data);
}
/**
* @param {?} value
* @return {?}
*/
set userData(value) {
this.store(this.storage_user_data, value);
}
/**
* @return {?}
*/
get authNonce() {
return this.retrieve(this.storage_auth_nonce) || '';
}
/**
* @param {?} value
* @return {?}
*/
set authNonce(value) {
this.store(this.storage_auth_nonce, value);
}
/**
* @return {?}
*/
get code_verifier() {
return this.retrieve(this.storage_code_verifier) || '';
}
/**
* @param {?} value
* @return {?}
*/
set code_verifier(value) {
this.store(this.storage_code_verifier, value);
}
/**
* @return {?}
*/
get authStateControl() {
return this.retrieve(this.storage_auth_state_control) || '';
}
/**
* @param {?} value
* @return {?}
*/
set authStateControl(value) {
this.store(this.storage_auth_state_control, value);
}
/**
* @return {?}
*/
get sessionState() {
return this.retrieve(this.storage_session_state);
}
/**
* @param {?} value
* @return {?}
*/
set sessionState(value) {
this.store(this.storage_session_state, value);
}
/**
* @return {?}
*/
get silentRenewRunning() {
return this.retrieve(this.storage_silent_renew_running) || '';
}
/**
* @param {?} value
* @return {?}
*/
set silentRenewRunning(value) {
this.store(this.storage_silent_renew_running, value);
}
/**
* @return {?}
*/
get customRequestParams() {
return this.retrieve(this.storage_custom_request_params);
}
/**
* @param {?} value
* @return {?}
*/
set customRequestParams(value) {
this.store(this.storage_custom_request_params, value);
}
/**
* @private
* @param {?} key
* @return {?}
*/
retrieve(key) {
return this.oidcSecurityStorage.read(key);
}
/**
* @private
* @param {?} key
* @param {?} value
* @return {?}
*/
store(key, value) {
this.oidcSecurityStorage.write(key, value);
}
/**
* @param {?} isRenewProcess
* @return {?}
*/
resetStorageData(isRenewProcess) {
if (!isRenewProcess) {
this.store(this.storage_auth_result, '');
this.store(this.storage_session_state, '');
this.store(this.storage_silent_renew_running, '');
this.store(this.storage_is_authorized, false);
this.store(this.storage_access_token, '');
this.store(this.storage_id_token, '');
this.store(this.storage_user_data, '');
this.store(this.storage_code_verifier, '');
}
}
/**
* @return {?}
*/
getAccessToken() {
return this.retrieve(this.storage_access_token);
}
/**
* @return {?}
*/
getIdToken() {
return this.retrieve(this.storage_id_token);
}
/**
* @return {?}
*/
getRefreshToken() {
return this.authResult.refresh_token;
}
}
OidcSecurityCommon.decorators = [
{ type: Injectable }
];
/** @nocollapse */
OidcSecurityCommon.ctorParameters = () => [
{ type: OidcSecurityStorage }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
// http://openid.net/specs/openid-connect-implicit-1_0.html
// id_token
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
//
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
// or if it contains additional audiences not trusted by the Client.
//
// id_token C3: If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
//
// id_token C4: If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
//
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the
// alg Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
//
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the OpenID Connect Core 1.0
// [OpenID.Core] specification.
//
// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account
// for clock skew).
//
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
//
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one that was sent
// in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.The precise method for detecting replay attacks
// is Client specific.
//
// id_token C10: If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.
// The meaning and processing of acr Claim Values is out of scope for this document.
//
// id_token C11: When a max_age request is made, the Client SHOULD check the auth_time Claim value and request re- authentication
// if it determines too much time has elapsed since the last End- User authentication.
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash is present in the ID Token.
class OidcSecurityValidation {
/**
* @param {?} arrayHelperService
* @param {?} tokenHelperService
* @param {?} loggerService
*/
constructor(arrayHelperService, tokenHelperService, loggerService) {
this.arrayHelperService = arrayHelperService;
this.tokenHelperService = tokenHelperService;
this.loggerService = loggerService;
}
// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account for clock skew).
/**
* @param {?} token
* @param {?=} offsetSeconds
* @return {?}
*/
isTokenExpired(token, offsetSeconds) {
/** @type {?} */
let decoded;
decoded = this.tokenHelperService.getPayloadFromToken(token, false);
return !this.validate_id_token_exp_not_expired(decoded, offsetSeconds);
}
// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account for clock skew).
/**
* @param {?} decoded_id_token
* @param {?=} offsetSeconds
* @return {?}
*/
validate_id_token_exp_not_expired(decoded_id_token, offsetSeconds) {
/** @type {?} */
const tokenExpirationDate = this.tokenHelperService.getTokenExpirationDate(decoded_id_token);
offsetSeconds = offsetSeconds || 0;
if (!tokenExpirationDate) {
return false;
}
/** @type {?} */
const tokenExpirationValue = tokenExpirationDate.valueOf();
/** @type {?} */
const nowWithOffset = new Date().valueOf() + offsetSeconds * 1000;
/** @type {?} */
const tokenNotExpired = tokenExpirationValue > nowWithOffset;
this.loggerService.logDebug(`Token not expired?: ${tokenExpirationValue} > ${nowWithOffset} (${tokenNotExpired})`);
// Token not expired?
return tokenNotExpired;
}
// iss
// REQUIRED. Issuer Identifier for the Issuer of the response.The iss value is a case-sensitive URL using the https scheme that contains scheme, host,
// and optionally, port number and path components and no query or fragment components.
//
// sub
// REQUIRED. Subject Identifier.Locally unique and never reassigned identifier within the Issuer for the End- User,
// which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4.
// It MUST NOT exceed 255 ASCII characters in length.The sub value is a case-sensitive string.
//
// aud
// REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
// It MAY also contain identifiers for other audiences.In the general case, the aud value is an array of case-sensitive strings.
// In the common special case when there is one audience, the aud value MAY be a single case-sensitive string.
//
// exp
// REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.
// The processing of this parameter requires that the current date/ time MUST be before the expiration date/ time listed in the value.
// Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew.
// Its value is a JSON [RFC7159] number representing the number of seconds from 1970- 01 - 01T00: 00:00Z as measured in UTC until the date/ time.
// See RFC 3339 [RFC3339] for details regarding date/ times in general and UTC in particular.
//
// iat
// REQUIRED. Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from 1970- 01 - 01T00: 00:00Z as measured
// in UTC until the date/ time.
/**
* @param {?} dataIdToken
* @return {?}
*/
validate_required_id_token(dataIdToken) {
/** @type {?} */
let validated = true;
if (!dataIdToken.hasOwnProperty('iss')) {
validated = false;
this.loggerService.logWarning('iss is missing, this is required in the id_token');
}
if (!dataIdToken.hasOwnProperty('sub')) {
validated = false;
this.loggerService.logWarning('sub is missing, this is required in the id_token');
}
if (!dataIdToken.hasOwnProperty('aud')) {
validated = false;
this.loggerService.logWarning('aud is missing, this is required in the id_token');
}
if (!dataIdToken.hasOwnProperty('exp')) {
validated = false;
this.loggerService.logWarning('exp is missing, this is required in the id_token');
}
if (!dataIdToken.hasOwnProperty('iat')) {
validated = false;
this.loggerService.logWarning('iat is missing, this is required in the id_token');
}
return validated;
}
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
/**
* @param {?} dataIdToken
* @param {?} max_offset_allowed_in_seconds
* @param {?} disable_iat_offset_validation
* @return {?}
*/
validate_id_token_iat_max_offset(dataIdToken, max_offset_allowed_in_seconds, disable_iat_offset_validation) {
if (disable_iat_offset_validation) {
return true;
}
if (!dataIdToken.hasOwnProperty('iat')) {
return false;
}
/** @type {?} */
const dateTime_iat_id_token = new Date(0);
dateTime_iat_id_token.setUTCSeconds(dataIdToken.iat);
max_offset_allowed_in_seconds = max_offset_allowed_in_seconds || 0;
if (dateTime_iat_id_token == null) {
return false;
}
this.loggerService.logDebug('validate_id_token_iat_max_offset: ' +
(new Date().valueOf() - dateTime_iat_id_token.valueOf()) +
' < ' +
max_offset_allowed_in_seconds * 1000);
return new Date().valueOf() - dateTime_iat_id_token.valueOf() < max_offset_allowed_in_seconds * 1000;
}
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one
// that was sent in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.
// The precise method for detecting replay attacks is Client specific.
/**
* @param {?} dataIdToken
* @param {?} local_nonce
* @return {?}
*/
validate_id_token_nonce(dataIdToken, local_nonce) {
/** @type {?} */
const isFromRefreshToken = dataIdToken.nonce === undefined && local_nonce === OidcSecurityValidation.RefreshTokenNoncePlaceholder;
if (!isFromRefreshToken && dataIdToken.nonce !== local_nonce) {
this.loggerService.logDebug('Validate_id_token_nonce failed, dataIdToken.nonce: ' + dataIdToken.nonce + ' local_nonce:' + local_nonce);
return false;
}
return true;
}
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
/**
* @param {?} dataIdToken
* @param {?} authWellKnownEndpoints_issuer
* @return {?}
*/
validate_id_token_iss(dataIdToken, authWellKnownEndpoints_issuer) {
if (((/** @type {?} */ (dataIdToken.iss))) !== ((/** @type {?} */ (authWellKnownEndpoints_issuer)))) {
this.loggerService.logDebug('Validate_id_token_iss failed, dataIdToken.iss: ' +
dataIdToken.iss +
' authWellKnownEndpoints issuer:' +
authWellKnownEndpoints_issuer);
return false;
}
return true;
}
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences
// not trusted by the Client.
/**
* @param {?} dataIdToken
* @param {?} aud
* @return {?}
*/
validate_id_token_aud(dataIdToken, aud) {
if (dataIdToken.aud instanceof Array) {
/** @type {?} */
const result = this.arrayHelperService.areEqual(dataIdToken.aud, aud);
if (!result) {
this.loggerService.logDebug('Validate_id_token_aud array failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud);
return false;
}
return true;
}
else if (dataIdToken.aud !== aud) {
this.loggerService.logDebug('Validate_id_token_aud failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud);
return false;
}
return true;
}
/**
* @param {?} state
* @param {?} local_state
* @return {?}
*/
validateStateFromHashCallback(state, local_state) {
if (((/** @type {?} */ (state))) !== ((/** @type {?} */ (local_state)))) {
this.loggerService.logDebug('ValidateStateFromHashCallback failed, state: ' + state + ' local_state:' + local_state);
return false;
}
return true;
}
/**
* @param {?} id_token_sub
* @param {?} userdata_sub
* @return {?}
*/
validate_userdata_sub_id_token(id_token_sub, userdata_sub) {
if (((/** @type {?} */ (id_token_sub))) !== ((/** @type {?} */ (userdata_sub)))) {
this.loggerService.logDebug('validate_userdata_sub_id_token failed, id_token_sub: ' + id_token_sub + ' userdata_sub:' + userdata_sub);
return false;
}
return true;
}
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the alg
// Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the
// OpenID Connect Core 1.0 [OpenID.Core] specification.
/**
* @param {?} id_token
* @param {?} jwtkeys
* @return {?}
*/
validate_signature_id_token(id_token, jwtkeys) {
if (!jwtkeys || !jwtkeys.keys) {
return false;
}
/** @type {?} */
const header_data = this.tokenHelperService.getHeaderFromToken(id_token, false);
if (Object.keys(header_data).length === 0 && header_data.constructor === Object) {
this.loggerService.logWarning('id token has no header data');
return false;
}
/** @type {?} */
const kid = header_data.kid;
/** @type {?} */
const alg = header_data.alg;
if ('RS256' !== ((/** @type {?} */ (alg)))) {
this.loggerService.logWarning('Only RS256 supported');
return false;
}
/** @type {?} */
let isValid = false;
if (!header_data.hasOwnProperty('kid')) {
// exactly 1 key in the jwtkeys and no kid in the Jose header
// kty "RSA" use "sig"
/** @type {?} */
let amountOfMatchingKeys = 0;
for (const key of jwtkeys.keys) {
if (((/** @type {?} */ (key.kty))) === 'RSA' && ((/** @type {?} */ (key.use))) === 'sig') {
amountOfMatchingKeys = amountOfMatchingKeys + 1;
}
}
if (amountOfMatchingKeys === 0) {
this.loggerService.logWarning('no keys found, incorrect Signature, validation failed for id_token');
return false;
}
else if (amountOfMatchingKeys > 1) {
this.loggerService.logWarning('no ID Token kid claim in JOSE header and multiple supplied in jwks_uri');
return false;
}
else {
for (const key of jwtkeys.keys) {
if (((/** @type {?} */ (key.kty))) === 'RSA' && ((/** @type {?} */ (key.use))) === 'sig') {
/** @type {?} */
const publickey = KEYUTIL.getKey(key);
isValid = KJUR.jws.JWS.verify(id_token, publickey, ['RS256']);
if (!isValid) {
this.loggerService.logWarning('incorrect Signature, validation failed for id_token');
}
return isValid;
}
}
}
}
else {
// kid in the Jose header of id_token
for (const key of jwtkeys.keys) {
if (((/** @type {?} */ (key.kid))) === ((/** @type {?} */ (kid)))) {
/** @type {?} */
const publickey = KEYUTIL.getKey(key);
isValid = KJUR.jws.JWS.verify(id_token, publickey, ['RS256']);
if (!isValid) {
this.loggerService.logWarning('incorrect Signature, validation failed for id_token');
}
return isValid;
}
}
}
return isValid;
}
/**
* @param {?} response_type
* @return {?}
*/
config_validate_response_type(response_type) {
if (response_type === 'id_token token' || response_type === 'id_token') {
return true;
}
if (response_type === 'code') {
return true;
}
this.loggerService.logWarning('module configure incorrect, invalid response_type:' + response_type);
return false;
}
// Accepts ID Token without 'kid' claim in JOSE header if only one JWK supplied in 'jwks_url'
//// private validate_no_kid_in_header_only_one_allowed_in_jwtkeys(header_data: any, jwtkeys: any): boolean {
//// this.oidcSecurityCommon.logDebug('amount of jwtkeys.keys: ' + jwtkeys.keys.length);
//// if (!header_data.hasOwnProperty('kid')) {
//// // no kid defined in Jose header
//// if (jwtkeys.keys.length != 1) {
//// this.oidcSecurityCommon.logDebug('jwtkeys.keys.length != 1 and no kid in header');
//// return false;
//// }
//// }
//// return true;
//// }
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash
// is present in the ID Token.
/**
* @param {?} access_token
* @param {?} at_hash
* @param {?} isCodeFlow
* @return {?}
*/
validate_id_token_at_hash(access_token, at_hash, isCodeFlow) {
this.loggerService.logDebug('at_hash from the server:' + at_hash);
// The at_hash is optional for the code flow
if (isCodeFlow) {
if (!((/** @type {?} */ (at_hash)))) {
this.loggerService.logDebug('Code Flow active, and no at_hash in the id_token, skipping check!');
return true;
}
}
/** @type {?} */
const testdata = this.generate_at_hash('' + access_token);
this.loggerService.logDebug('at_hash client validation not decoded:' + testdata);
if (testdata === ((/** @type {?} */ (at_hash)))) {
return true; // isValid;
}
else {
/** @type {?} */
const testValue = this.generate_at_hash('' + decodeURIComponent(access_token));
this.loggerService.logDebug('-gen access--' + testValue);
if (testValue === ((/** @type {?} */ (at_hash)))) {
return true; // isValid
}
}
return false;
}
/**
* @private
* @param {?} access_token
* @return {?}
*/
generate_at_hash(access_token) {
/** @type {?} */
const hash = KJUR.crypto.Util.hashString(access_token, 'sha256');
/** @type {?} */
const first128bits = hash.substr(0, hash.length / 2);
/** @type {?} */
const testdata = hextob64u(first128bits);
return testdata;
}
/**
* @param {?} code_challenge
* @return {?}
*/
generate_code_verifier(code_challenge) {
/** @type {?} */
const hash = KJUR.crypto.Util.hashString(code_challenge, 'sha256');
/** @type {?} */
const testdata = hextob64u(hash);
return testdata;
}
}
OidcSecurityValidation.RefreshTokenNoncePlaceholder = '--RefreshToken--';
OidcSecurityValidation.decorators = [
{ type: Injectable }
];
/** @nocollapse */
OidcSecurityValidation.ctorParameters = () => [
{ type: EqualityHelperService },
{ type: TokenHelperService },
{ type: LoggerService }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class StateValidationService {
/**
* @param {?} oidcSecurityCommon
* @param {?} oidcSecurityValidation
* @param {?} tokenHelperService
* @param {?} loggerService
* @param {?} configurationProvider
*/
constructor(oidcSecurityCommon, oidcSecurityValidation, tokenHelperService, loggerService, configurationProvider) {
this.oidcSecurityCommon = oidcSecurityCommon;
this.oidcSecurityValidation = oidcSecurityValidation;
this.tokenHelperService = tokenHelperService;
this.loggerService = loggerService;
this.configurationProvider = configurationProvider;
}
/**
* @param {?} result
* @param {?} jwtKeys
* @return {?}
*/
validateState(result, jwtKeys) {
/** @type {?} */
const toReturn = new ValidateStateResult();
if (!this.oidcSecurityValidation.validateStateFromHashCallback(result.state, this.oidcSecurityCommon.authStateControl)) {
this.loggerService.l