angular-simple-oidc
Version:
Angular Library implementing Open Id Connect specification. Code Flow, Refresh Tokens, Session Management, Discovery Document.
1,028 lines (1,017 loc) • 64.8 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('jsrsasign'), require('@angular/common/http')) :
typeof define === 'function' && define.amd ? define('angular-simple-oidc/core', ['exports', '@angular/core', 'jsrsasign', '@angular/common/http'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global["angular-simple-oidc"] = global["angular-simple-oidc"] || {}, global["angular-simple-oidc"].core = {}), global.ng.core, global.jsrsasign, global.ng.common.http));
})(this, (function (exports, core, jsrsasign, http) { 'use strict';
var TokenCryptoService = /** @class */ (function () {
function TokenCryptoService() {
}
TokenCryptoService.prototype.sha256b64First128Bits = function (payload) {
var hash = jsrsasign.KJUR.crypto.Util.hashString(payload, 'sha256');
var first128bits = hash.substr(0, hash.length / 2);
return jsrsasign.hextob64u(first128bits);
};
TokenCryptoService.prototype.sha256btoa = function (payload) {
var hash = jsrsasign.KJUR.crypto.Util.hashString(payload, 'sha256');
return jsrsasign.hextob64u(hash);
};
TokenCryptoService.prototype.verifySignature = function (key, message) {
var pk = jsrsasign.KEYUTIL.getKey(key);
return jsrsasign.KJUR.jws.JWS.verify(message, pk, ['RS256']);
};
TokenCryptoService.prototype.generateNonce = function () {
return 'N' + Math.random() + '' + Date.now();
};
TokenCryptoService.prototype.generateState = function () {
return Date.now() + '' + Math.random() + Math.random();
};
TokenCryptoService.prototype.generateCodesForCodeVerification = function () {
var codeVerifier = 'C' + Math.random() + '' + Date.now() + '' + Date.now() + Math.random();
var codeChallenge = this.sha256btoa(codeVerifier);
var method = 'S256';
return {
codeVerifier: codeVerifier,
codeChallenge: codeChallenge,
method: method
};
};
return TokenCryptoService;
}());
TokenCryptoService.decorators = [
{ type: core.Injectable }
];
/**
* Inspired on https://github.com/damienbod/angular-auth-oidc-client
*/
var TokenHelperService = /** @class */ (function () {
function TokenHelperService() {
}
TokenHelperService.prototype.convertTokenClaimToDate = function (claim) {
if (!claim) {
return null;
}
var date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(claim);
return date;
};
TokenHelperService.prototype.isTokenExpired = function (expiresAt) {
return new Date().getTime() > expiresAt;
};
TokenHelperService.prototype.getExpirationFromExpiresIn = function (expiresIn) {
var now = new Date();
// expires_in = access token expiration in seconds (optional)
// 3.2.2.5. Successful Authentication Response
// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
now.setSeconds(now.getSeconds() + expiresIn);
return now;
};
TokenHelperService.prototype.getHeaderFromToken = function (idToken) {
return this.getTokenSlice(idToken, 0);
};
TokenHelperService.prototype.getPayloadFromToken = function (idToken) {
return this.getTokenSlice(idToken, 1);
};
TokenHelperService.prototype.getSignatureFromToken = function (idToken) {
return this.getTokenSlice(idToken, 2);
};
TokenHelperService.prototype.getTokenSlice = function (idToken, index) {
if (!idToken || idToken.split('.').length !== 3) {
// Quick and dirty validation.
// The caller is expcetd to validate the token properly
return null;
}
var slice = idToken.split('.')[index];
var result = jsrsasign.b64utoutf8(slice);
return JSON.parse(result);
};
return TokenHelperService;
}());
TokenHelperService.decorators = [
{ type: core.Injectable }
];
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b)
if (Object.prototype.hasOwnProperty.call(b, p))
d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function () {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s)
if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s)
if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if (d = decorators[i])
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); };
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try {
step(generator.next(value));
}
catch (e) {
reject(e);
} }
function rejected(value) { try {
step(generator["throw"](value));
}
catch (e) {
reject(e);
} }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function () { if (t[0] & 1)
throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f)
throw new TypeError("Generator is already executing.");
while (_)
try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)
return t;
if (y = 0, t)
op = [op[0] & 2, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return { value: op[1], done: false };
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2])
_.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
}
catch (e) {
op = [6, e];
y = 0;
}
finally {
f = t = 0;
}
if (op[0] & 5)
throw op[1];
return { value: op[0] ? op[1] : void 0, done: true };
}
}
var __createBinding = Object.create ? (function (o, m, k, k2) {
if (k2 === undefined)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
}) : (function (o, m, k, k2) {
if (k2 === undefined)
k2 = k;
o[k2] = m[k];
});
function __exportStar(m, o) {
for (var p in m)
if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p))
__createBinding(o, m, p);
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m)
return m.call(o);
if (o && typeof o.length === "number")
return {
next: function () {
if (o && i >= o.length)
o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m)
return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
ar.push(r.value);
}
catch (error) {
e = { error: error };
}
finally {
try {
if (r && !r.done && (m = i["return"]))
m.call(i);
}
finally {
if (e)
throw e.error;
}
}
return ar;
}
/** @deprecated */
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
/** @deprecated */
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++)
s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2)
for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar)
ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n])
i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try {
step(g[n](v));
}
catch (e) {
settle(q[0][3], e);
} }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length)
resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) {
Object.defineProperty(cooked, "raw", { value: raw });
}
else {
cooked.raw = raw;
}
return cooked;
}
;
var __setModuleDefault = Object.create ? (function (o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function (o, v) {
o["default"] = v;
};
function __importStar(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null)
for (var k in mod)
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}
function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m")
throw new TypeError("Private method is not writable");
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
}
var SimpleOidcError = /** @class */ (function (_super) {
__extends(SimpleOidcError, _super);
function SimpleOidcError(message, code, context) {
var _this = _super.call(this, message) || this;
_this.code = code;
_this.context = context;
_this.name = code;
return _this;
}
return SimpleOidcError;
}(Error));
var RequiredParemetersMissingError = /** @class */ (function (_super) {
__extends(RequiredParemetersMissingError, _super);
function RequiredParemetersMissingError(paramName, context) {
return _super.call(this, "Expected a valid value in provided parameter: " + paramName, 'required-param-missing', context) || this;
}
return RequiredParemetersMissingError;
}(SimpleOidcError));
function validateObjectRequiredProps(obj, props) {
var e_1, _a;
try {
for (var props_1 = __values(props), props_1_1 = props_1.next(); !props_1_1.done; props_1_1 = props_1.next()) {
var key = props_1_1.value;
if (!obj.hasOwnProperty(key)) {
throw new RequiredParemetersMissingError(key.toString(), {
object: obj,
requiredProps: props
});
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (props_1_1 && !props_1_1.done && (_a = props_1.return)) _a.call(props_1);
}
finally { if (e_1) throw e_1.error; }
}
}
var TokenUrlService = /** @class */ (function () {
function TokenUrlService(tokenCrypto) {
this.tokenCrypto = tokenCrypto;
}
TokenUrlService.prototype.createAuthorizationCodeRequestPayload = function (params) {
var httpParams = new http.HttpParams()
.set('client_id', params.clientId)
.set('client_secret', params.clientSecret)
.set('grant_type', 'authorization_code')
.set('code_verifier', params.codeVerifier)
.set('code', params.code)
.set('redirect_uri', params.redirectUri);
if (params.scope) {
httpParams = httpParams
.set('scope', params.scope);
}
if (params.acrValues) {
httpParams = httpParams
.set('acr_values', params.acrValues);
}
return httpParams.toString();
};
TokenUrlService.prototype.createRefreshTokenRequestPayload = function (params) {
var httpParams = new http.HttpParams()
.set('client_id', params.clientId)
.set('client_secret', params.clientSecret)
.set('grant_type', 'refresh_token')
.set('refresh_token', params.refreshToken);
if (params.scope) {
httpParams = httpParams
.set('scope', params.scope);
}
if (params.acrValues) {
httpParams = httpParams
.set('acr_values', params.acrValues);
}
return httpParams.toString();
};
TokenUrlService.prototype.createAuthorizeUrl = function (authorizeEndpointUrl, params) {
if (!authorizeEndpointUrl || !authorizeEndpointUrl.length) {
throw new RequiredParemetersMissingError("authorizeEndpointUrl", arguments);
}
if (!params) {
throw new RequiredParemetersMissingError("params", arguments);
}
validateObjectRequiredProps(params, ['clientId', 'redirectUri', 'scope', 'responseType']);
var state = this.tokenCrypto.generateState();
var nonce = this.tokenCrypto.generateNonce();
var verification = this.tokenCrypto.generateCodesForCodeVerification();
var httpParams = new http.HttpParams()
.set('client_id', params.clientId)
.set('scope', params.scope)
.set('redirect_uri', params.redirectUri)
.set('response_type', params.responseType)
.set('state', state)
.set('nonce', nonce)
.set('code_challenge', verification.codeChallenge)
.set('code_challenge_method', verification.method);
if (params.prompt) {
httpParams = httpParams.set('prompt', params.prompt);
}
if (params.loginHint) {
httpParams = httpParams.set('login_hint', params.loginHint);
}
if (params.uiLocales) {
httpParams = httpParams.set('ui_locales', params.uiLocales);
}
if (params.acrValues) {
httpParams = httpParams.set('acr_values', params.acrValues);
}
if (params.idTokenHint) {
httpParams = httpParams.set('id_token_hint', params.idTokenHint);
}
if (params.display) {
httpParams = httpParams.set('display', params.display);
}
var url = authorizeEndpointUrl + "?" + httpParams;
return {
nonce: nonce,
state: state,
codeVerifier: verification.codeVerifier,
codeChallenge: verification.codeChallenge,
url: url,
};
};
TokenUrlService.prototype.createEndSessionUrl = function (endSessionEndpointUrl, params) {
if (params === void 0) { params = {}; }
if (!endSessionEndpointUrl || !endSessionEndpointUrl.length) {
throw new RequiredParemetersMissingError("endSessionEndpointUrl", arguments);
}
var state = this.tokenCrypto.generateState();
var httpParams = new http.HttpParams()
.set('state', state);
if (params.idTokenHint) {
httpParams = httpParams
.set('id_token_hint', params.idTokenHint);
}
if (params.postLogoutRedirectUri) {
httpParams = httpParams
.set('post_logout_redirect_uri', params.postLogoutRedirectUri);
}
var url = endSessionEndpointUrl + "?" + httpParams;
return {
url: url,
state: state
};
};
TokenUrlService.prototype.parseAuthorizeCallbackParamsFromUrl = function (url) {
if (!url || !url.length) {
throw new RequiredParemetersMissingError("url", arguments);
}
var paramsError = new RequiredParemetersMissingError("url must have params", arguments);
if (!url.includes('?')) {
throw paramsError;
}
var params = new http.HttpParams({
fromString: url.split('?')[1],
});
if (!params.keys().length) {
throw paramsError;
}
return {
code: params.get('code'),
state: params.get('state'),
error: params.get('error'),
sessionState: params.get('session_state')
};
};
return TokenUrlService;
}());
TokenUrlService.decorators = [
{ type: core.Injectable }
];
TokenUrlService.ctorParameters = function () { return [
{ type: TokenCryptoService }
]; };
var TokenValidationError = /** @class */ (function (_super) {
__extends(TokenValidationError, _super);
function TokenValidationError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return TokenValidationError;
}(SimpleOidcError));
var IdentityTokenMalformedError = /** @class */ (function (_super) {
__extends(IdentityTokenMalformedError, _super);
function IdentityTokenMalformedError(context) {
return _super.call(this, 'Identity token format invalid: it needs to have three dots. (header.payload.signature)', 'id-token-invalid-format', context) || this;
}
return IdentityTokenMalformedError;
}(TokenValidationError));
var JWTKeysMissingError = /** @class */ (function (_super) {
__extends(JWTKeysMissingError, _super);
function JWTKeysMissingError(context) {
return _super.call(this, 'Provided JWT Keys are empty or invalid', 'jwt-keys-empty', context) || this;
}
return JWTKeysMissingError;
}(TokenValidationError));
var JWTKeysInvalidError = /** @class */ (function (_super) {
__extends(JWTKeysInvalidError, _super);
function JWTKeysInvalidError(context) {
return _super.call(this, 'Failed to find a valid key from provided JWT Keys. No key with kty=RSA and use=sig found.', 'jwt-keys-invalid', context) || this;
}
return JWTKeysInvalidError;
}(TokenValidationError));
var InvalidSignatureError = /** @class */ (function (_super) {
__extends(InvalidSignatureError, _super);
function InvalidSignatureError(context) {
return _super.call(this, 'Failed to validate signature against any of the JWT keys', 'invalid-signature', context) || this;
}
return InvalidSignatureError;
}(TokenValidationError));
var SignatureAlgorithmNotSupportedError = /** @class */ (function (_super) {
__extends(SignatureAlgorithmNotSupportedError, _super);
function SignatureAlgorithmNotSupportedError(context) {
return _super.call(this, 'Only "RS256" alg is currently supported', 'signature-alg-not-supported', context) || this;
}
return SignatureAlgorithmNotSupportedError;
}(TokenValidationError));
var ClaimRequiredError = /** @class */ (function (_super) {
__extends(ClaimRequiredError, _super);
function ClaimRequiredError(claim, context) {
return _super.call(this, "Required claim " + claim + " is missing", "missing-claim", context) || this;
}
return ClaimRequiredError;
}(TokenValidationError));
var ClaimTypeInvalidError = /** @class */ (function (_super) {
__extends(ClaimTypeInvalidError, _super);
function ClaimTypeInvalidError(claim, expectedType, actualType, context) {
return _super.call(this, "Claim " + claim + " is expected to be " + expectedType + " got " + actualType + " instead.", "invalid-claim-type", context) || this;
}
return ClaimTypeInvalidError;
}(TokenValidationError));
var DateClaimInvalidError = /** @class */ (function (_super) {
__extends(DateClaimInvalidError, _super);
function DateClaimInvalidError(claim, context) {
return _super.call(this, "Failed to parse claim " + claim + " value into a Date object.", "invalid-date-claim", context) || this;
}
return DateClaimInvalidError;
}(TokenValidationError));
var IssuedAtValidationFailedError = /** @class */ (function (_super) {
__extends(IssuedAtValidationFailedError, _super);
function IssuedAtValidationFailedError(offset, context) {
return _super.call(this, "Issued at (iat claim) validation failed. Token was expected to have been issued between " + offset + " seconds offset", "iat-validation-failed", context) || this;
}
return IssuedAtValidationFailedError;
}(TokenValidationError));
var IssuerValidationFailedError = /** @class */ (function (_super) {
__extends(IssuerValidationFailedError, _super);
function IssuerValidationFailedError(identityTokenIssuer, discoveryIssuer, context) {
return _super.call(this,
// tslint:disable-next-line: max-line-length
"Issuer (iss) validation failed. Identity Token's iss (" + identityTokenIssuer + ") does not match discovery document's issuer (" + discoveryIssuer + ")", "iss-validation-failed", context) || this;
}
return IssuerValidationFailedError;
}(TokenValidationError));
var AudienceValidationFailedError = /** @class */ (function (_super) {
__extends(AudienceValidationFailedError, _super);
function AudienceValidationFailedError(identityTokenAud, clientId, context) {
return _super.call(this,
// tslint:disable-next-line: max-line-length
"Audience (aud) validation failed. Identity Token's aud (" + identityTokenAud + ") does not include this client's ID (" + clientId + "). The token may not intended for this client.", "aud-validation-failed", context) || this;
}
return AudienceValidationFailedError;
}(TokenValidationError));
var TokenExpiredError = /** @class */ (function (_super) {
__extends(TokenExpiredError, _super);
function TokenExpiredError(expiration, context) {
return _super.call(this, "The token has already expired at " + expiration, "token-expired", context) || this;
}
return TokenExpiredError;
}(TokenValidationError));
var AccessTokenHashValidationFailedError = /** @class */ (function (_super) {
__extends(AccessTokenHashValidationFailedError, _super);
function AccessTokenHashValidationFailedError(context) {
return _super.call(this, "Access Token Hash (at_hash) validation has failed. at_hash does not match hash of access token", "access-token-validation-failed", context) || this;
}
return AccessTokenHashValidationFailedError;
}(TokenValidationError));
var InvalidStateError = /** @class */ (function (_super) {
__extends(InvalidStateError, _super);
function InvalidStateError(context) {
return _super.call(this, 'State returned by IDP does not match local stored state.' +
'Are you performing multiple authorize calls at the same time?', 'invalid-state', context) || this;
}
return InvalidStateError;
}(SimpleOidcError));
var InvalidNonceError = /** @class */ (function (_super) {
__extends(InvalidNonceError, _super);
function InvalidNonceError(context) {
return _super.call(this, 'Nonce returned by IDP does not match local stored nonce.' +
'Are you performing multiple authorize calls at the same time?', 'invalid-nonce', context) || this;
}
return InvalidNonceError;
}(TokenValidationError));
var AuthorizationCallbackFormatError = /** @class */ (function (_super) {
__extends(AuthorizationCallbackFormatError, _super);
function AuthorizationCallbackFormatError(context) {
return _super.call(this, "IDP redirected with invalid URL", "authorize-callback-format", context) || this;
}
return AuthorizationCallbackFormatError;
}(SimpleOidcError));
var AuthorizationCallbackMissingParameterError = /** @class */ (function (_super) {
__extends(AuthorizationCallbackMissingParameterError, _super);
function AuthorizationCallbackMissingParameterError(param, context) {
return _super.call(this, "IDP redirected with invalid/missing parameters on the URL: " + param, "authorize-callback-missing-" + param, context) || this;
}
return AuthorizationCallbackMissingParameterError;
}(SimpleOidcError));
var AuthorizationCallbackError = /** @class */ (function (_super) {
__extends(AuthorizationCallbackError, _super);
function AuthorizationCallbackError(error, context) {
return _super.call(this, "IDP returned an error after authorization redirection: " + error, "authorize-callback-error", context) || this;
}
return AuthorizationCallbackError;
}(SimpleOidcError));
/**
* Implements Identity and Access tokens validations according to the
* Open ID Connect specification.
* https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
* Inspiration taken from https://github.com/damienbod/angular-auth-oidc-client
*/
var TokenValidationService = /** @class */ (function () {
function TokenValidationService(tokenHelper, tokenCrypto) {
this.tokenHelper = tokenHelper;
this.tokenCrypto = tokenCrypto;
}
TokenValidationService.prototype.validateIdToken = function (thisClientId, idToken, decodedIdToken, nonce, discoveryDocument, jwtKeys, tokenValidationConfig) {
// Apply all validation as defined on
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
this.validateIdTokenSignature(idToken, jwtKeys);
this.validateIdTokenNonce(decodedIdToken, nonce);
this.validateIdTokenRequiredFields(decodedIdToken);
this.validateIdTokenIssuedAt(decodedIdToken, tokenValidationConfig);
this.validateIdTokenIssuer(decodedIdToken, discoveryDocument.issuer);
this.validateIdTokenAud(decodedIdToken, thisClientId);
this.validateIdTokenExpiration(decodedIdToken);
};
/**
* The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
* MUST exactly match the value of the iss (issuer) Claim.
*/
TokenValidationService.prototype.validateIdTokenIssuer = function (idToken, discoveryDocumentIssuer) {
if (idToken.iss !== discoveryDocumentIssuer) {
throw new IssuerValidationFailedError(idToken.iss, discoveryDocumentIssuer, {
idToken: idToken,
discoveryDocumentIssuer: discoveryDocumentIssuer
});
}
};
/**
* Access Token Validation
* Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in 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.
* Take the left- most half of the hash and base64url- encode it.
* 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
*/
TokenValidationService.prototype.validateAccessToken = function (accessToken, idTokenAtHash) {
// The at_hash is optional for the code flow
if (!idTokenAtHash) {
console.info("No \"at_hash\" in Identity Token: Skipping access token validation.");
return;
}
var accessTokenHash = this.tokenCrypto.sha256b64First128Bits(accessToken);
if (idTokenAtHash !== accessTokenHash) {
throw new AccessTokenHashValidationFailedError({
accessToken: accessToken,
idTokenAtHash: idTokenAtHash,
calculatedHash: accessTokenHash
});
}
};
/**
* 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
*/
TokenValidationService.prototype.validateIdTokenAud = function (idToken, thisClientId) {
var aud = idToken.aud;
if (!Array.isArray(aud)) {
aud = [aud];
}
var valid = aud.includes(thisClientId);
if (!valid) {
throw new AudienceValidationFailedError(aud.join(','), thisClientId, {
idToken: idToken,
thisClientId: thisClientId,
aud: aud
});
}
};
/**
* The Client MUST validate the signature of the ID Token according to JWS using the algorithm
* specified in the alg Header Parameter of the JOSE Header.
* The Client MUST use the keys provided by the Issuer.
* The alg value SHOULD be RS256.
* Validation of tokens using other signing algorithms is described in the
* OpenID Connect Core 1.0 specification.
*/
TokenValidationService.prototype.validateIdTokenSignature = function (idToken, jwtKeys) {
var e_1, _a;
if (!jwtKeys || !jwtKeys.keys || !jwtKeys.keys.length) {
throw new JWTKeysMissingError({
idToken: idToken,
jwtKeys: jwtKeys
});
}
var header = this.tokenHelper.getHeaderFromToken(idToken);
if (header.alg !== 'RS256') {
throw new SignatureAlgorithmNotSupportedError({
idToken: idToken,
jwtKeys: jwtKeys,
header: header,
});
}
// Filter keys according to kty and use
var keysToTry = jwtKeys.keys
.filter(function (k) { return k.kty === 'RSA' && k.use === 'sig'; });
if (!keysToTry.length) {
throw new JWTKeysInvalidError({
idToken: idToken,
jwtKeys: jwtKeys,
header: header,
keysToTry: keysToTry
});
}
// Token header may have a 'kid' claim (key id)
// which determines which JWT key should be used for validation
// If present, must be a case sensitive string.
// https://tools.ietf.org/html/rfc7515#section-4.1.4
// https://tools.ietf.org/html/rfc7515#appendix-D
var kid;
if (header.kid && typeof header.kid === 'string' && header.kid.length) {
if (keysToTry.some(function (k) { return k.kid === header.kid; })) {
// Threat the kid as a hint, prioritizing it's key
// but still trying the other keys if the desired key fails.
kid = header.kid;
keysToTry = keysToTry.sort(function (k) { return k.kid === kid ? -1 : 1; });
}
else {
console.info("Identity token's header contained 'kid'\n but no key with that kid was found on JWT Keys.\n Will still try to validate using other keys, if any.\n kid: " + header.kid + ",\n ValidKeys kids: " + JSON.stringify(keysToTry.map(function (k) { return k.kid; })));
}
}
try {
// Validate each key returning as soon as one suceeds
for (var keysToTry_1 = __values(keysToTry), keysToTry_1_1 = keysToTry_1.next(); !keysToTry_1_1.done; keysToTry_1_1 = keysToTry_1.next()) {
var key = keysToTry_1_1.value;
if (this.tokenCrypto.verifySignature(key, idToken)) {
if (kid && kid !== key.kid) {
console.info("Identity token's header contained 'kid' " + kid + "\n but key signature was validated using key " + key.kid);
}
return;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (keysToTry_1_1 && !keysToTry_1_1.done && (_a = keysToTry_1.return)) _a.call(keysToTry_1);
}
finally { if (e_1) throw e_1.error; }
}
throw new InvalidSignatureError({
idToken: idToken,
jwtKeys: jwtKeys,
header: header,
keysToTry: keysToTry,
kid: kid
});
};
/**
* The current time MUST be before the time represented by the exp Claim
* (possibly allowing for some small leeway to account for clock skew)
*/
TokenValidationService.prototype.validateIdTokenExpiration = function (idToken, offsetSeconds) {
this.validateTokenNumericClaim(idToken, 'exp');
var tokenExpirationDate = this.tokenHelper.convertTokenClaimToDate(idToken.exp);
if (!tokenExpirationDate) {
throw new DateClaimInvalidError('exp', {
idToken: idToken,
offsetSeconds: offsetSeconds,
parsedDate: tokenExpirationDate
});
}
offsetSeconds = offsetSeconds || 0;
var tokenExpirationMs = tokenExpirationDate.valueOf();
var maxDateMs = new Date().valueOf() - offsetSeconds * 1000;
var tokenNotExpired = tokenExpirationMs > maxDateMs;
if (!tokenNotExpired) {
throw new TokenExpiredError(tokenExpirationDate, {
idToken: idToken,
offsetSeconds: offsetSeconds,
tokenExpirationDate: tokenExpirationDate,
tokenExpirationMs: tokenExpirationMs,
maxDateMs: maxDateMs,
maxDate: new Date(maxDateMs)
});
}
};
/**
* 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.
*/
TokenValidationService.prototype.validateIdTokenIssuedAt = function (idToken, config) {
if (config === void 0) { config = {}; }
if (config.disableIdTokenIATValidation) {
console.info('Issued At validation has been disabled by configuration');
return;
}
this.validateTokenNumericClaim(idToken, 'iat');
var idTokenIATDate = this.tokenHelper.convertTokenClaimToDate(idToken.iat);
if (!idTokenIATDate) {
throw new DateClaimInvalidError('iat', {
idToken: idToken,
config: config,
parsedDate: idTokenIATDate
});
}
var maxOffsetInMs = (config.idTokenIATOffsetAllowed || 5) * 1000;
var now = new Date().valueOf();
var valid = (now - idTokenIATDate.valueOf()) < maxOffsetInMs;
if (!valid) {
throw new IssuedAtValidationFailedError(maxOffsetInMs / 1000, {
idToken: idToken,
config: config,
iatDiff: now - idTokenIATDate.valueOf(),
maxOffsetInMs: maxOffsetInMs,
});
}
};
/**
* 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.
*/
TokenValidationService.prototype.validateIdTokenNonce = function (idToken, localNonce) {
if (idToken.nonce !== localNonce) {
throw new InvalidNonceError({
localNonce: localNonce,
idTokenNonce: idToken.nonce,
idToken: idToken
});
}
};
/**
* 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.
*/
TokenValidationService.prototype.validateIdTokenRequiredFields = function (idToken) {
var e_2, _a;
var requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
try {
for (var requiredClaims_1 = __values(requiredClaims), requiredClaims_1_1 = requiredClaims_1.next(); !requiredClaims_1_1.done; requiredClaims_1_1 = requiredClaims_1.next()) {
var key = requiredClaims_1_1.value;
if (!idToken.hasOwnProperty(key)) {
throw new ClaimRequiredError(key, {
idToken: idToken,
requiredClaims: requiredClaims,
missingClaim: key
});
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (requiredClaims_1_1 && !requiredClaims_1_1.done && (_a = requiredClaims_1.return)) _a.call(requiredClaims_1);
}
finally { if (e_2) throw e_2.error; }
}
};
/**
* Validates that an expected token numeric field is a number on runtime.
*/
TokenValidationService.prototype.validateTokenNumericClaim = function (idToken, claim) {
if (typeof idToken[claim] !== 'number') {
if (!idToken[claim]) {
throw new ClaimRequiredError(claim.toString(), {
idToken: idToken,
requiredClaim: claim
});