angular-simple-oidc
Version:
Angular Library implementing Open Id Connect specification. Code Flow, Refresh Tokens, Session Management, Discovery Document.
863 lines (849 loc) • 35.5 kB
JavaScript
import { Injectable, NgModule } from '@angular/core';
import { KJUR, hextob64u, KEYUTIL, b64utoutf8 } from 'jsrsasign';
import { HttpParams } from '@angular/common/http';
class TokenCryptoService {
sha256b64First128Bits(payload) {
const hash = KJUR.crypto.Util.hashString(payload, 'sha256');
const first128bits = hash.substr(0, hash.length / 2);
return hextob64u(first128bits);
}
sha256btoa(payload) {
const hash = KJUR.crypto.Util.hashString(payload, 'sha256');
return hextob64u(hash);
}
verifySignature(key, message) {
const pk = KEYUTIL.getKey(key);
return KJUR.jws.JWS.verify(message, pk, ['RS256']);
}
generateNonce() {
return 'N' + Math.random() + '' + Date.now();
}
generateState() {
return Date.now() + '' + Math.random() + Math.random();
}
generateCodesForCodeVerification() {
const codeVerifier = 'C' + Math.random() + '' + Date.now() + '' + Date.now() + Math.random();
const codeChallenge = this.sha256btoa(codeVerifier);
const method = 'S256';
return {
codeVerifier,
codeChallenge,
method
};
}
}
TokenCryptoService.decorators = [
{ type: Injectable }
];
/**
* Inspired on https://github.com/damienbod/angular-auth-oidc-client
*/
class TokenHelperService {
convertTokenClaimToDate(claim) {
if (!claim) {
return null;
}
const date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(claim);
return date;
}
isTokenExpired(expiresAt) {
return new Date().getTime() > expiresAt;
}
getExpirationFromExpiresIn(expiresIn) {
const 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;
}
getHeaderFromToken(idToken) {
return this.getTokenSlice(idToken, 0);
}
getPayloadFromToken(idToken) {
return this.getTokenSlice(idToken, 1);
}
getSignatureFromToken(idToken) {
return this.getTokenSlice(idToken, 2);
}
getTokenSlice(idToken, index) {
if (!idToken || idToken.split('.').length !== 3) {
// Quick and dirty validation.
// The caller is expcetd to validate the token properly
return null;
}
const slice = idToken.split('.')[index];
const result = b64utoutf8(slice);
return JSON.parse(result);
}
}
TokenHelperService.decorators = [
{ type: Injectable }
];
class SimpleOidcError extends Error {
constructor(message, code, context) {
super(message);
this.code = code;
this.context = context;
this.name = code;
}
}
class RequiredParemetersMissingError extends SimpleOidcError {
constructor(paramName, context) {
super(`Expected a valid value in provided parameter: ${paramName}`, 'required-param-missing', context);
}
}
function validateObjectRequiredProps(obj, props) {
for (const key of props) {
if (!obj.hasOwnProperty(key)) {
throw new RequiredParemetersMissingError(key.toString(), {
object: obj,
requiredProps: props
});
}
}
}
class TokenUrlService {
constructor(tokenCrypto) {
this.tokenCrypto = tokenCrypto;
}
createAuthorizationCodeRequestPayload(params) {
let httpParams = new 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();
}
createRefreshTokenRequestPayload(params) {
let httpParams = new 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();
}
createAuthorizeUrl(authorizeEndpointUrl, params) {
if (!authorizeEndpointUrl || !authorizeEndpointUrl.length) {
throw new RequiredParemetersMissingError(`authorizeEndpointUrl`, arguments);
}
if (!params) {
throw new RequiredParemetersMissingError(`params`, arguments);
}
validateObjectRequiredProps(params, ['clientId', 'redirectUri', 'scope', 'responseType']);
const state = this.tokenCrypto.generateState();
const nonce = this.tokenCrypto.generateNonce();
const verification = this.tokenCrypto.generateCodesForCodeVerification();
let httpParams = new 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);
}
const url = `${authorizeEndpointUrl}?${httpParams}`;
return {
nonce,
state,
codeVerifier: verification.codeVerifier,
codeChallenge: verification.codeChallenge,
url,
};
}
createEndSessionUrl(endSessionEndpointUrl, params = {}) {
if (!endSessionEndpointUrl || !endSessionEndpointUrl.length) {
throw new RequiredParemetersMissingError(`endSessionEndpointUrl`, arguments);
}
const state = this.tokenCrypto.generateState();
let httpParams = new 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);
}
const url = `${endSessionEndpointUrl}?${httpParams}`;
return {
url,
state
};
}
parseAuthorizeCallbackParamsFromUrl(url) {
if (!url || !url.length) {
throw new RequiredParemetersMissingError(`url`, arguments);
}
const paramsError = new RequiredParemetersMissingError(`url must have params`, arguments);
if (!url.includes('?')) {
throw paramsError;
}
const params = new 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')
};
}
}
TokenUrlService.decorators = [
{ type: Injectable }
];
TokenUrlService.ctorParameters = () => [
{ type: TokenCryptoService }
];
class TokenValidationError extends SimpleOidcError {
}
class IdentityTokenMalformedError extends TokenValidationError {
constructor(context) {
super('Identity token format invalid: it needs to have three dots. (header.payload.signature)', 'id-token-invalid-format', context);
}
}
class JWTKeysMissingError extends TokenValidationError {
constructor(context) {
super('Provided JWT Keys are empty or invalid', 'jwt-keys-empty', context);
}
}
class JWTKeysInvalidError extends TokenValidationError {
constructor(context) {
super('Failed to find a valid key from provided JWT Keys. No key with kty=RSA and use=sig found.', 'jwt-keys-invalid', context);
}
}
class InvalidSignatureError extends TokenValidationError {
constructor(context) {
super('Failed to validate signature against any of the JWT keys', 'invalid-signature', context);
}
}
class SignatureAlgorithmNotSupportedError extends TokenValidationError {
constructor(context) {
super('Only "RS256" alg is currently supported', 'signature-alg-not-supported', context);
}
}
class ClaimRequiredError extends TokenValidationError {
constructor(claim, context) {
super(`Required claim ${claim} is missing`, `missing-claim`, context);
}
}
class ClaimTypeInvalidError extends TokenValidationError {
constructor(claim, expectedType, actualType, context) {
super(`Claim ${claim} is expected to be ${expectedType} got ${actualType} instead.`, `invalid-claim-type`, context);
}
}
class DateClaimInvalidError extends TokenValidationError {
constructor(claim, context) {
super(`Failed to parse claim ${claim} value into a Date object.`, `invalid-date-claim`, context);
}
}
class IssuedAtValidationFailedError extends TokenValidationError {
constructor(offset, context) {
super(`Issued at (iat claim) validation failed. Token was expected to have been issued between ${offset} seconds offset`, `iat-validation-failed`, context);
}
}
class IssuerValidationFailedError extends TokenValidationError {
constructor(identityTokenIssuer, discoveryIssuer, context) {
super(
// 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);
}
}
class AudienceValidationFailedError extends TokenValidationError {
constructor(identityTokenAud, clientId, context) {
super(
// 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);
}
}
class TokenExpiredError extends TokenValidationError {
constructor(expiration, context) {
super(`The token has already expired at ${expiration}`, `token-expired`, context);
}
}
class AccessTokenHashValidationFailedError extends TokenValidationError {
constructor(context) {
super(`Access Token Hash (at_hash) validation has failed. at_hash does not match hash of access token`, `access-token-validation-failed`, context);
}
}
class InvalidStateError extends SimpleOidcError {
constructor(context) {
super('State returned by IDP does not match local stored state.' +
'Are you performing multiple authorize calls at the same time?', 'invalid-state', context);
}
}
class InvalidNonceError extends TokenValidationError {
constructor(context) {
super('Nonce returned by IDP does not match local stored nonce.' +
'Are you performing multiple authorize calls at the same time?', 'invalid-nonce', context);
}
}
class AuthorizationCallbackFormatError extends SimpleOidcError {
constructor(context) {
super(`IDP redirected with invalid URL`, `authorize-callback-format`, context);
}
}
class AuthorizationCallbackMissingParameterError extends SimpleOidcError {
constructor(param, context) {
super(`IDP redirected with invalid/missing parameters on the URL: ${param}`, `authorize-callback-missing-${param}`, context);
}
}
class AuthorizationCallbackError extends SimpleOidcError {
constructor(error, context) {
super(`IDP returned an error after authorization redirection: ${error}`, `authorize-callback-error`, context);
}
}
/**
* 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
*/
class TokenValidationService {
constructor(tokenHelper, tokenCrypto) {
this.tokenHelper = tokenHelper;
this.tokenCrypto = tokenCrypto;
}
validateIdToken(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.
*/
validateIdTokenIssuer(idToken, discoveryDocumentIssuer) {
if (idToken.iss !== discoveryDocumentIssuer) {
throw new IssuerValidationFailedError(idToken.iss, discoveryDocumentIssuer, {
idToken,
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
*/
validateAccessToken(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;
}
const accessTokenHash = this.tokenCrypto.sha256b64First128Bits(accessToken);
if (idTokenAtHash !== accessTokenHash) {
throw new AccessTokenHashValidationFailedError({
accessToken,
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
*/
validateIdTokenAud(idToken, thisClientId) {
let aud = idToken.aud;
if (!Array.isArray(aud)) {
aud = [aud];
}
const valid = aud.includes(thisClientId);
if (!valid) {
throw new AudienceValidationFailedError(aud.join(','), thisClientId, {
idToken,
thisClientId,
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.
*/
validateIdTokenSignature(idToken, jwtKeys) {
if (!jwtKeys || !jwtKeys.keys || !jwtKeys.keys.length) {
throw new JWTKeysMissingError({
idToken,
jwtKeys
});
}
const header = this.tokenHelper.getHeaderFromToken(idToken);
if (header.alg !== 'RS256') {
throw new SignatureAlgorithmNotSupportedError({
idToken,
jwtKeys,
header,
});
}
// Filter keys according to kty and use
let keysToTry = jwtKeys.keys
.filter(k => k.kty === 'RSA' && k.use === 'sig');
if (!keysToTry.length) {
throw new JWTKeysInvalidError({
idToken,
jwtKeys,
header,
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
let kid;
if (header.kid && typeof header.kid === 'string' && header.kid.length) {
if (keysToTry.some(k => 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(k => k.kid === kid ? -1 : 1);
}
else {
console.info(`Identity token's header contained 'kid'
but no key with that kid was found on JWT Keys.
Will still try to validate using other keys, if any.
kid: ${header.kid},
ValidKeys kids: ${JSON.stringify(keysToTry.map(k => k.kid))}`);
}
}
// Validate each key returning as soon as one suceeds
for (const key of keysToTry) {
if (this.tokenCrypto.verifySignature(key, idToken)) {
if (kid && kid !== key.kid) {
console.info(`Identity token's header contained 'kid' ${kid}
but key signature was validated using key ${key.kid}`);
}
return;
}
}
throw new InvalidSignatureError({
idToken,
jwtKeys,
header,
keysToTry,
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)
*/
validateIdTokenExpiration(idToken, offsetSeconds) {
this.validateTokenNumericClaim(idToken, 'exp');
const tokenExpirationDate = this.tokenHelper.convertTokenClaimToDate(idToken.exp);
if (!tokenExpirationDate) {
throw new DateClaimInvalidError('exp', {
idToken,
offsetSeconds,
parsedDate: tokenExpirationDate
});
}
offsetSeconds = offsetSeconds || 0;
const tokenExpirationMs = tokenExpirationDate.valueOf();
const maxDateMs = new Date().valueOf() - offsetSeconds * 1000;
const tokenNotExpired = tokenExpirationMs > maxDateMs;
if (!tokenNotExpired) {
throw new TokenExpiredError(tokenExpirationDate, {
idToken,
offsetSeconds,
tokenExpirationDate,
tokenExpirationMs,
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.
*/
validateIdTokenIssuedAt(idToken, config = {}) {
if (config.disableIdTokenIATValidation) {
console.info('Issued At validation has been disabled by configuration');
return;
}
this.validateTokenNumericClaim(idToken, 'iat');
const idTokenIATDate = this.tokenHelper.convertTokenClaimToDate(idToken.iat);
if (!idTokenIATDate) {
throw new DateClaimInvalidError('iat', {
idToken,
config,
parsedDate: idTokenIATDate
});
}
const maxOffsetInMs = (config.idTokenIATOffsetAllowed || 5) * 1000;
const now = new Date().valueOf();
const valid = (now - idTokenIATDate.valueOf()) < maxOffsetInMs;
if (!valid) {
throw new IssuedAtValidationFailedError(maxOffsetInMs / 1000, {
idToken,
config,
iatDiff: now - idTokenIATDate.valueOf(),
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.
*/
validateIdTokenNonce(idToken, localNonce) {
if (idToken.nonce !== localNonce) {
throw new InvalidNonceError({
localNonce,
idTokenNonce: idToken.nonce,
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.
*/
validateIdTokenRequiredFields(idToken) {
const requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
for (const key of requiredClaims) {
if (!idToken.hasOwnProperty(key)) {
throw new ClaimRequiredError(key, {
idToken,
requiredClaims,
missingClaim: key
});
}
}
}
/**
* Validates that an expected token numeric field is a number on runtime.
*/
validateTokenNumericClaim(idToken, claim) {
if (typeof idToken[claim] !== 'number') {
if (!idToken[claim]) {
throw new ClaimRequiredError(claim.toString(), {
idToken,
requiredClaim: claim
});
}
else {
throw new ClaimTypeInvalidError(claim.toString(), 'number', typeof (idToken[claim]), {
idToken,
claim,
claimType: typeof (idToken[claim]),
claimExpectedType: 'number'
});
}
}
}
/**
* Makes sure that the format of the identity token is correct.
* It needs to be a non-empty string and contain three dots
*/
validateIdTokenFormat(idToken) {
if (!idToken || !idToken.length) {
throw new RequiredParemetersMissingError('idToken', null);
}
const expectedSliceAmount = 3;
const slices = idToken.split('.');
if (slices.length !== expectedSliceAmount) {
throw new IdentityTokenMalformedError({
idToken
});
}
}
/**
* Validates the local state against the
* returned state from the IDP to make sure it matches
*/
validateAuthorizeCallbackState(localState, state) {
if (state !== localState) {
throw new InvalidStateError({
localState,
returnedState: state,
});
}
}
validateAuthorizeCallbackFormat(code, state, error, href) {
if (typeof error === 'string') {
throw new AuthorizationCallbackError(error, {
url: href,
error,
});
}
if (typeof code !== 'string') {
throw new AuthorizationCallbackMissingParameterError('code', {
url: href,
});
}
if (typeof state !== 'string') {
throw new AuthorizationCallbackMissingParameterError('state', {
url: href,
});
}
}
}
TokenValidationService.decorators = [
{ type: Injectable }
];
TokenValidationService.ctorParameters = () => [
{ type: TokenHelperService },
{ type: TokenCryptoService }
];
class IssuerValidationError extends TokenValidationError {
constructor(originalIssuer, newIssuer, context) {
super(
// tslint:disable-next-line: max-line-length
`Issuer (iss) validation failed. Original Identity Token's iss (${originalIssuer}) does not match new token's issuer (${newIssuer})`, `iss-validation-failed-refresh`, context);
}
}
class SubjectValidationError extends TokenValidationError {
constructor(originalSubject, newSubject, context) {
super(
// tslint:disable-next-line: max-line-length
`Subject (sub) validation failed. Original Identity Token's sub (${originalSubject}) does not match new token's sub (${newSubject})`, `sub-validation-failed-refresh`, context);
}
}
class IssuedAtValidationError extends TokenValidationError {
constructor(context) {
super(`Issued At (iat) validation failed. Expected new iat to represent the time of the new token creation.`, `iat-validation-failed-refresh`, context);
}
}
class AudienceValidationError extends TokenValidationError {
constructor(context) {
super(`Audience (aud) validation failed. Original Identity Token's aud does not match new token's aud`, `aud-validation-failed-refresh`, context);
}
}
class AuthTimeValidationError extends TokenValidationError {
constructor(context) {
super(`Auth Time (auth_time) validation failed. Original Identity Token's auth_time does not match new token's auth_time`, `auth-time-validation-failed-refresh`, context);
}
}
class AuthorizedPartyValidationError extends TokenValidationError {
constructor(context) {
super(`Authorized Party (azp) validation failed. Original Identity Token's azp does not match new token's azp`, `azp-validation-failed-refresh`, context);
}
}
/**
* its iss Claim Value MUST be the same as in the ID Token issued when the original authentication occurred,
* its sub Claim Value MUST be the same as in the ID Token issued when the original authentication occurred,
* its iat Claim MUST represent the time that the new ID Token is issued,
* its aud Claim Value MUST be the same as in the ID Token issued when the original authentication occurred,
* if the ID Token contains an auth_time Claim, its value MUST represent the time
* of the original authentication - not the time that the new ID token is issued,
* its azp Claim Value MUST be the same as in the ID Token issued when the original authentication occurred;
* if no azp Claim was present in the original ID Token, one MUST NOT be present in the new ID Token, and
* otherwise, the same rules apply as apply when issuing an ID Token at the time of the original authentication.
*/
class RefreshTokenValidationService {
/**
* Perform validations according to
* 12.2. Successful Refresh Response
* https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
*/
validateIdToken(originalIdToken, newIdToken) {
this.validateIssuer(originalIdToken, newIdToken);
this.validateSubject(originalIdToken, newIdToken);
this.validateIssuedAt(originalIdToken, newIdToken);
this.validateAudience(originalIdToken, newIdToken);
this.validateAuthTime(originalIdToken, newIdToken);
this.validateAuthorizedParty(originalIdToken, newIdToken);
}
/**
* its iss Claim Value MUST be the same as in the ID Token issued when the original authentication occurred
*/
validateIssuer(originalIdToken, newIdToken) {
if (originalIdToken.iss !== newIdToken.iss) {
throw new IssuerValidationError(originalIdToken.iss, newIdToken.iss, {
originalIdToken,
newIdToken
});
}
}
/**
* its sub Claim Value MUST be the same as in the ID Token issued when the original authentication occurred,
*/
validateSubject(originalIdToken, newIdToken) {
if (originalIdToken.sub !== newIdToken.sub) {
throw new SubjectValidationError(originalIdToken.sub, newIdToken.sub, {
originalIdToken,
newIdToken
});
}
}
/**
* its iat Claim MUST represent the time that the new ID Token is issued,
*/
validateIssuedAt(originalIdToken, newIdToken) {
if (!(newIdToken.iat >= originalIdToken.iat)) {
throw new IssuedAtValidationError({
originalIdToken,
newIdToken
});
}
}
/**
* its aud Claim Value MUST be the same as in the ID Token issued when the original authentication occurred,
*/
validateAudience(originalIdToken, newIdToken) {
if (originalIdToken.aud !== newIdToken.aud) {
throw new AudienceValidationError({
originalIdToken,
newIdToken
});
}
}
/**
* if the ID Token contains an auth_time Claim, its value MUST represent
* the time of the original authentication - not the time that the new ID token is issued,
*/
validateAuthTime(originalIdToken, newIdToken) {
if (newIdToken.auth_time && (originalIdToken.auth_time !== newIdToken.auth_time)) {
throw new AuthTimeValidationError({
originalIdToken,
newIdToken
});
}
}
/**
* its azp Claim Value MUST be the same as in the ID Token issued when the original authentication occurred;
* if no azp Claim was present in the original ID Token, one MUST NOT be present in the new ID Token, and
* otherwise, the same rules apply as apply when issuing an ID Token at the time of the original authentication.
*/
validateAuthorizedParty(originalIdToken, newIdToken) {
if (originalIdToken.azp !== newIdToken.azp) {
throw new AuthorizedPartyValidationError({
originalIdToken,
newIdToken
});
}
}
}
RefreshTokenValidationService.decorators = [
{ type: Injectable }
];
class AngularSimpleOidcCoreModule {
}
AngularSimpleOidcCoreModule.decorators = [
{ type: NgModule, args: [{
imports: [],
providers: [
TokenCryptoService,
TokenUrlService,
TokenHelperService,
TokenValidationService,
RefreshTokenValidationService,
],
declarations: [],
},] }
];
var TokenStorageKeys;
(function (TokenStorageKeys) {
TokenStorageKeys["Nonce"] = "simple.oidc.nonce";
TokenStorageKeys["State"] = "simple.oidc.state";
TokenStorageKeys["CodeVerifier"] = "simple.oidc.code-verifier";
TokenStorageKeys["AuthorizationCode"] = "simple.oidc.authorization-code";
TokenStorageKeys["SessionState"] = "simple.oidc.session-state";
TokenStorageKeys["OriginalIdentityToken"] = "simple.oidc.original-identity-token";
TokenStorageKeys["IdentityToken"] = "simple.oidc.identity-token";
TokenStorageKeys["IdentityTokenDecoded"] = "simple.oidc.identity-token-decoded";
TokenStorageKeys["AccessToken"] = "simple.oidc.access-token";
TokenStorageKeys["RefreshToken"] = "simple.oidc.refresh-token";
TokenStorageKeys["AccessTokenExpiration"] = "simple.oidc.access-token-expiration";
TokenStorageKeys["PreRedirectUrl"] = "simple.oidc.pre-redirect-url";
})(TokenStorageKeys || (TokenStorageKeys = {}));
/**
* Generated bundle index. Do not edit.
*/
export { AccessTokenHashValidationFailedError, AngularSimpleOidcCoreModule, AudienceValidationError, AudienceValidationFailedError, AuthTimeValidationError, AuthorizationCallbackError, AuthorizationCallbackFormatError, AuthorizationCallbackMissingParameterError, AuthorizedPartyValidationError, ClaimRequiredError, ClaimTypeInvalidError, DateClaimInvalidError, IdentityTokenMalformedError, InvalidNonceError, InvalidSignatureError, InvalidStateError, IssuedAtValidationError, IssuedAtValidationFailedError, IssuerValidationError, IssuerValidationFailedError, JWTKeysInvalidError, JWTKeysMissingError, RefreshTokenValidationService, RequiredParemetersMissingError, SignatureAlgorithmNotSupportedError, SimpleOidcError, SubjectValidationError, TokenCryptoService, TokenExpiredError, TokenHelperService, TokenStorageKeys, TokenUrlService, TokenValidationError, TokenValidationService };
//# sourceMappingURL=angular-simple-oidc-core.js.map