UNPKG

126 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { Injectable, Optional, Inject, makeEnvironmentProviders, NgModule, InjectionToken } from '@angular/core';
3import { DOCUMENT, CommonModule } from '@angular/common';
4import * as i1 from '@angular/common/http';
5import { HttpHeaders, HttpParams, HTTP_INTERCEPTORS } from '@angular/common/http';
6import { Subject, of, from, race, throwError, combineLatest, merge } from 'rxjs';
7import { filter, tap, debounceTime, delay, map, switchMap, first, catchError, take, mergeMap, timeout } from 'rxjs/operators';
8
9/**
10 * A validation handler that isn't validating nothing.
11 * Can be used to skip validation (at your own risk).
12 */
13class NullValidationHandler {
14 validateSignature(validationParams) {
15 return Promise.resolve(null);
16 }
17 validateAtHash(validationParams) {
18 return Promise.resolve(true);
19 }
20}
21
22class OAuthModuleConfig {
23}
24class OAuthResourceServerConfig {
25}
26
27class DateTimeProvider {
28}
29class SystemDateTimeProvider extends DateTimeProvider {
30 now() {
31 return Date.now();
32 }
33 new() {
34 return new Date();
35 }
36 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: SystemDateTimeProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
37 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: SystemDateTimeProvider }); }
38}
39i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: SystemDateTimeProvider, decorators: [{
40 type: Injectable
41 }] });
42
43/**
44 * Additional options that can be passed to tryLogin.
45 */
46class LoginOptions {
47 constructor() {
48 /**
49 * Set this to true to disable the nonce
50 * check which is used to avoid
51 * replay attacks.
52 * This flag should never be true in
53 * production environments.
54 */
55 this.disableNonceCheck = false;
56 /**
57 * Normally, you want to clear your hash fragment after
58 * the lib read the token(s) so that they are not displayed
59 * anymore in the url. If not, set this to true. For code flow
60 * this controls removing query string values.
61 */
62 this.preventClearHashAfterLogin = false;
63 }
64}
65/**
66 * Defines the logging interface the OAuthService uses
67 * internally. Is compatible with the `console` object,
68 * but you can provide your own implementation as well
69 * through dependency injection.
70 */
71class OAuthLogger {
72}
73/**
74 * Defines a simple storage that can be used for
75 * storing the tokens at client side.
76 * Is compatible to localStorage and sessionStorage,
77 * but you can also create your own implementations.
78 */
79class OAuthStorage {
80}
81class MemoryStorage {
82 constructor() {
83 this.data = new Map();
84 }
85 getItem(key) {
86 return this.data.get(key);
87 }
88 removeItem(key) {
89 this.data.delete(key);
90 }
91 setItem(key, data) {
92 this.data.set(key, data);
93 }
94 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: MemoryStorage, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
95 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: MemoryStorage }); }
96}
97i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: MemoryStorage, decorators: [{
98 type: Injectable
99 }] });
100/**
101 * Represents the received tokens, the received state
102 * and the parsed claims from the id-token.
103 */
104class ReceivedTokens {
105}
106
107class OAuthEvent {
108 constructor(type) {
109 this.type = type;
110 }
111}
112class OAuthSuccessEvent extends OAuthEvent {
113 constructor(type, info = null) {
114 super(type);
115 this.info = info;
116 }
117}
118class OAuthInfoEvent extends OAuthEvent {
119 constructor(type, info = null) {
120 super(type);
121 this.info = info;
122 }
123}
124class OAuthErrorEvent extends OAuthEvent {
125 constructor(type, reason, params = null) {
126 super(type);
127 this.reason = reason;
128 this.params = params;
129 }
130}
131
132// see: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
133function b64DecodeUnicode(str) {
134 const base64 = str.replace(/-/g, '+').replace(/_/g, '/');
135 return decodeURIComponent(atob(base64)
136 .split('')
137 .map(function (c) {
138 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
139 })
140 .join(''));
141}
142function base64UrlEncode(str) {
143 const base64 = btoa(str);
144 return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
145}
146
147class AuthConfig {
148 constructor(json) {
149 /**
150 * The client's id as registered with the auth server
151 */
152 this.clientId = '';
153 /**
154 * The client's redirectUri as registered with the auth server
155 */
156 this.redirectUri = '';
157 /**
158 * An optional second redirectUri where the auth server
159 * redirects the user to after logging out.
160 */
161 this.postLogoutRedirectUri = '';
162 /**
163 * Defines whether to use 'redirectUri' as a replacement
164 * of 'postLogoutRedirectUri' if the latter is not set.
165 */
166 this.redirectUriAsPostLogoutRedirectUriFallback = true;
167 /**
168 * The auth server's endpoint that allows to log
169 * the user in when using implicit flow.
170 */
171 this.loginUrl = '';
172 /**
173 * The requested scopes
174 */
175 this.scope = 'openid profile';
176 this.resource = '';
177 this.rngUrl = '';
178 /**
179 * Defines whether to use OpenId Connect during
180 * implicit flow.
181 */
182 this.oidc = true;
183 /**
184 * Defines whether to request an access token during
185 * implicit flow.
186 */
187 this.requestAccessToken = true;
188 this.options = null;
189 /**
190 * The issuer's uri.
191 */
192 this.issuer = '';
193 /**
194 * The logout url.
195 */
196 this.logoutUrl = '';
197 /**
198 * Defines whether to clear the hash fragment after logging in.
199 */
200 this.clearHashAfterLogin = true;
201 /**
202 * Url of the token endpoint as defined by OpenId Connect and OAuth 2.
203 */
204 this.tokenEndpoint = null;
205 /**
206 * Url of the revocation endpoint as defined by OpenId Connect and OAuth 2.
207 */
208 this.revocationEndpoint = null;
209 /**
210 * Names of known parameters sent out in the TokenResponse. https://tools.ietf.org/html/rfc6749#section-5.1
211 */
212 this.customTokenParameters = [];
213 /**
214 * Url of the userinfo endpoint as defined by OpenId Connect.
215 */
216 this.userinfoEndpoint = null;
217 this.responseType = '';
218 /**
219 * Defines whether additional debug information should
220 * be shown at the console. Note that in certain browsers
221 * the verbosity of the console needs to be explicitly set
222 * to include Debug level messages.
223 */
224 this.showDebugInformation = false;
225 /**
226 * The redirect uri used when doing silent refresh.
227 */
228 this.silentRefreshRedirectUri = '';
229 this.silentRefreshMessagePrefix = '';
230 /**
231 * Set this to true to display the iframe used for
232 * silent refresh for debugging.
233 */
234 this.silentRefreshShowIFrame = false;
235 /**
236 * Timeout for silent refresh.
237 * @internal
238 * @deprecated use silentRefreshTimeout
239 */
240 this.siletRefreshTimeout = 1000 * 20;
241 /**
242 * Timeout for silent refresh.
243 */
244 this.silentRefreshTimeout = 1000 * 20;
245 /**
246 * Some auth servers don't allow using password flow
247 * w/o a client secret while the standards do not
248 * demand for it. In this case, you can set a password
249 * here. As this password is exposed to the public
250 * it does not bring additional security and is therefore
251 * as good as using no password.
252 */
253 this.dummyClientSecret = '';
254 /**
255 * Defines whether https is required.
256 * The default value is remoteOnly which only allows
257 * http for localhost, while every other domains need
258 * to be used with https.
259 */
260 this.requireHttps = 'remoteOnly';
261 /**
262 * Defines whether every url provided by the discovery
263 * document has to start with the issuer's url.
264 */
265 this.strictDiscoveryDocumentValidation = true;
266 /**
267 * JSON Web Key Set (https://tools.ietf.org/html/rfc7517)
268 * with keys used to validate received id_tokens.
269 * This is taken out of the disovery document. Can be set manually too.
270 */
271 this.jwks = null;
272 /**
273 * Map with additional query parameter that are appended to
274 * the request when initializing implicit flow.
275 */
276 this.customQueryParams = null;
277 this.silentRefreshIFrameName = 'angular-oauth-oidc-silent-refresh-iframe';
278 /**
279 * Defines when the token_timeout event should be raised.
280 * If you set this to the default value 0.75, the event
281 * is triggered after 75% of the token's life time.
282 */
283 this.timeoutFactor = 0.75;
284 /**
285 * If true, the lib will try to check whether the user
286 * is still logged in on a regular basis as described
287 * in http://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification
288 */
289 this.sessionChecksEnabled = false;
290 /**
291 * Interval in msec for checking the session
292 * according to http://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification
293 */
294 this.sessionCheckIntervall = 3 * 1000;
295 /**
296 * Url for the iframe used for session checks
297 */
298 this.sessionCheckIFrameUrl = null;
299 /**
300 * Name of the iframe to use for session checks
301 */
302 this.sessionCheckIFrameName = 'angular-oauth-oidc-check-session-iframe';
303 /**
304 * This property has been introduced to disable at_hash checks
305 * and is indented for Identity Provider that does not deliver
306 * an at_hash EVEN THOUGH its recommended by the OIDC specs.
307 * Of course, when disabling these checks then we are bypassing
308 * a security check which means we are more vulnerable.
309 */
310 this.disableAtHashCheck = false;
311 /**
312 * Defines wether to check the subject of a refreshed token after silent refresh.
313 * Normally, it should be the same as before.
314 */
315 this.skipSubjectCheck = false;
316 this.useIdTokenHintForSilentRefresh = false;
317 /**
318 * Defined whether to skip the validation of the issuer in the discovery document.
319 * Normally, the discovey document's url starts with the url of the issuer.
320 */
321 this.skipIssuerCheck = false;
322 /**
323 * final state sent to issuer is built as follows:
324 * state = nonce + nonceStateSeparator + additional state
325 * Default separator is ';' (encoded %3B).
326 * In rare cases, this character might be forbidden or inconvenient to use by the issuer so it can be customized.
327 */
328 this.nonceStateSeparator = ';';
329 /**
330 * Set this to true to use HTTP BASIC auth for AJAX calls
331 */
332 this.useHttpBasicAuth = false;
333 /**
334 * Decreases the Expiration time of tokens by this number of seconds
335 */
336 this.decreaseExpirationBySec = 0;
337 /**
338 * The interceptors waits this time span if there is no token
339 */
340 this.waitForTokenInMsec = 0;
341 /**
342 * Code Flow is by defauld used together with PKCI which is also higly recommented.
343 * You can disbale it here by setting this flag to true.
344 * https://tools.ietf.org/html/rfc7636#section-1.1
345 */
346 this.disablePKCE = false;
347 /**
348 * Set this to true to preserve the requested route including query parameters after code flow login.
349 * This setting enables deep linking for the code flow.
350 */
351 this.preserveRequestedRoute = false;
352 /**
353 * Allows to disable the timer for the id_token used
354 * for token refresh
355 */
356 this.disableIdTokenTimer = false;
357 /**
358 * Blocks other origins requesting a silent refresh
359 */
360 this.checkOrigin = false;
361 /**
362 * This property allows you to override the method that is used to open the login url,
363 * allowing a way for implementations to specify their own method of routing to new
364 * urls.
365 */
366 this.openUri = (uri) => {
367 location.href = uri;
368 };
369 if (json) {
370 Object.assign(this, json);
371 }
372 }
373}
374
375/**
376 * This custom encoder allows charactes like +, % and / to be used in passwords
377 */
378class WebHttpUrlEncodingCodec {
379 encodeKey(k) {
380 return encodeURIComponent(k);
381 }
382 encodeValue(v) {
383 return encodeURIComponent(v);
384 }
385 decodeKey(k) {
386 return decodeURIComponent(k);
387 }
388 decodeValue(v) {
389 return decodeURIComponent(v);
390 }
391}
392
393/**
394 * Interface for Handlers that are hooked in to
395 * validate tokens.
396 */
397class ValidationHandler {
398}
399/**
400 * This abstract implementation of ValidationHandler already implements
401 * the method validateAtHash. However, to make use of it,
402 * you have to override the method calcHash.
403 */
404class AbstractValidationHandler {
405 /**
406 * Validates the at_hash in an id_token against the received access_token.
407 */
408 async validateAtHash(params) {
409 const hashAlg = this.inferHashAlgorithm(params.idTokenHeader);
410 const tokenHash = await this.calcHash(params.accessToken, hashAlg); // sha256(accessToken, { asString: true });
411 const leftMostHalf = tokenHash.substr(0, tokenHash.length / 2);
412 const atHash = base64UrlEncode(leftMostHalf);
413 const claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, '');
414 if (atHash !== claimsAtHash) {
415 console.error('exptected at_hash: ' + atHash);
416 console.error('actual at_hash: ' + claimsAtHash);
417 }
418 return atHash === claimsAtHash;
419 }
420 /**
421 * Infers the name of the hash algorithm to use
422 * from the alg field of an id_token.
423 *
424 * @param jwtHeader the id_token's parsed header
425 */
426 inferHashAlgorithm(jwtHeader) {
427 const alg = jwtHeader['alg'];
428 if (!alg.match(/^.S[0-9]{3}$/)) {
429 throw new Error('Algorithm not supported: ' + alg);
430 }
431 return 'sha-' + alg.substr(2);
432 }
433}
434
435class UrlHelperService {
436 getHashFragmentParams(customHashFragment) {
437 let hash = customHashFragment || window.location.hash;
438 hash = decodeURIComponent(hash);
439 if (hash.indexOf('#') !== 0) {
440 return {};
441 }
442 const questionMarkPosition = hash.indexOf('?');
443 if (questionMarkPosition > -1) {
444 hash = hash.substr(questionMarkPosition + 1);
445 }
446 else {
447 hash = hash.substr(1);
448 }
449 return this.parseQueryString(hash);
450 }
451 parseQueryString(queryString) {
452 const data = {};
453 let pair, separatorIndex, escapedKey, escapedValue, key, value;
454 if (queryString === null) {
455 return data;
456 }
457 const pairs = queryString.split('&');
458 for (let i = 0; i < pairs.length; i++) {
459 pair = pairs[i];
460 separatorIndex = pair.indexOf('=');
461 if (separatorIndex === -1) {
462 escapedKey = pair;
463 escapedValue = null;
464 }
465 else {
466 escapedKey = pair.substr(0, separatorIndex);
467 escapedValue = pair.substr(separatorIndex + 1);
468 }
469 key = decodeURIComponent(escapedKey);
470 value = decodeURIComponent(escapedValue);
471 if (key.substr(0, 1) === '/') {
472 key = key.substr(1);
473 }
474 data[key] = value;
475 }
476 return data;
477 }
478 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: UrlHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
479 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: UrlHelperService }); }
480}
481i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: UrlHelperService, decorators: [{
482 type: Injectable
483 }] });
484
485// Credits: https://github.com/dchest/fast-sha256-js/tree/master/src
486// We add this lib directly b/c the published version of fast-sha256-js
487// is commonjs and hence leads to a warning about tree-shakability emitted
488// by the Angular CLI
489// SHA-256 (+ HMAC and PBKDF2) for JavaScript.
490//
491// Written in 2014-2016 by Dmitry Chestnykh.
492// Public domain, no warranty.
493//
494// Functions (accept and return Uint8Arrays):
495//
496// sha256(message) -> hash
497// sha256.hmac(key, message) -> mac
498// sha256.pbkdf2(password, salt, rounds, dkLen) -> dk
499//
500// Classes:
501//
502// new sha256.Hash()
503// new sha256.HMAC(key)
504//
505const digestLength = 32;
506const blockSize = 64;
507// SHA-256 constants
508const K = new Uint32Array([
509 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
510 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
511 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
512 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
513 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
514 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
515 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
516 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
517 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
518 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
519 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
520]);
521function hashBlocks(w, v, p, pos, len) {
522 let a, b, c, d, e, f, g, h, u, i, j, t1, t2;
523 while (len >= 64) {
524 a = v[0];
525 b = v[1];
526 c = v[2];
527 d = v[3];
528 e = v[4];
529 f = v[5];
530 g = v[6];
531 h = v[7];
532 for (i = 0; i < 16; i++) {
533 j = pos + i * 4;
534 w[i] =
535 ((p[j] & 0xff) << 24) |
536 ((p[j + 1] & 0xff) << 16) |
537 ((p[j + 2] & 0xff) << 8) |
538 (p[j + 3] & 0xff);
539 }
540 for (i = 16; i < 64; i++) {
541 u = w[i - 2];
542 t1 =
543 ((u >>> 17) | (u << (32 - 17))) ^
544 ((u >>> 19) | (u << (32 - 19))) ^
545 (u >>> 10);
546 u = w[i - 15];
547 t2 =
548 ((u >>> 7) | (u << (32 - 7))) ^
549 ((u >>> 18) | (u << (32 - 18))) ^
550 (u >>> 3);
551 w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0);
552 }
553 for (i = 0; i < 64; i++) {
554 t1 =
555 ((((((e >>> 6) | (e << (32 - 6))) ^
556 ((e >>> 11) | (e << (32 - 11))) ^
557 ((e >>> 25) | (e << (32 - 25)))) +
558 ((e & f) ^ (~e & g))) |
559 0) +
560 ((h + ((K[i] + w[i]) | 0)) | 0)) |
561 0;
562 t2 =
563 ((((a >>> 2) | (a << (32 - 2))) ^
564 ((a >>> 13) | (a << (32 - 13))) ^
565 ((a >>> 22) | (a << (32 - 22)))) +
566 ((a & b) ^ (a & c) ^ (b & c))) |
567 0;
568 h = g;
569 g = f;
570 f = e;
571 e = (d + t1) | 0;
572 d = c;
573 c = b;
574 b = a;
575 a = (t1 + t2) | 0;
576 }
577 v[0] += a;
578 v[1] += b;
579 v[2] += c;
580 v[3] += d;
581 v[4] += e;
582 v[5] += f;
583 v[6] += g;
584 v[7] += h;
585 pos += 64;
586 len -= 64;
587 }
588 return pos;
589}
590// Hash implements SHA256 hash algorithm.
591class Hash {
592 constructor() {
593 this.digestLength = digestLength;
594 this.blockSize = blockSize;
595 // Note: Int32Array is used instead of Uint32Array for performance reasons.
596 this.state = new Int32Array(8); // hash state
597 this.temp = new Int32Array(64); // temporary state
598 this.buffer = new Uint8Array(128); // buffer for data to hash
599 this.bufferLength = 0; // number of bytes in buffer
600 this.bytesHashed = 0; // number of total bytes hashed
601 this.finished = false; // indicates whether the hash was finalized
602 this.reset();
603 }
604 // Resets hash state making it possible
605 // to re-use this instance to hash other data.
606 reset() {
607 this.state[0] = 0x6a09e667;
608 this.state[1] = 0xbb67ae85;
609 this.state[2] = 0x3c6ef372;
610 this.state[3] = 0xa54ff53a;
611 this.state[4] = 0x510e527f;
612 this.state[5] = 0x9b05688c;
613 this.state[6] = 0x1f83d9ab;
614 this.state[7] = 0x5be0cd19;
615 this.bufferLength = 0;
616 this.bytesHashed = 0;
617 this.finished = false;
618 return this;
619 }
620 // Cleans internal buffers and re-initializes hash state.
621 clean() {
622 for (let i = 0; i < this.buffer.length; i++) {
623 this.buffer[i] = 0;
624 }
625 for (let i = 0; i < this.temp.length; i++) {
626 this.temp[i] = 0;
627 }
628 this.reset();
629 }
630 // Updates hash state with the given data.
631 //
632 // Optionally, length of the data can be specified to hash
633 // fewer bytes than data.length.
634 //
635 // Throws error when trying to update already finalized hash:
636 // instance must be reset to use it again.
637 update(data, dataLength = data.length) {
638 if (this.finished) {
639 throw new Error("SHA256: can't update because hash was finished.");
640 }
641 let dataPos = 0;
642 this.bytesHashed += dataLength;
643 if (this.bufferLength > 0) {
644 while (this.bufferLength < 64 && dataLength > 0) {
645 this.buffer[this.bufferLength++] = data[dataPos++];
646 dataLength--;
647 }
648 if (this.bufferLength === 64) {
649 hashBlocks(this.temp, this.state, this.buffer, 0, 64);
650 this.bufferLength = 0;
651 }
652 }
653 if (dataLength >= 64) {
654 dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength);
655 dataLength %= 64;
656 }
657 while (dataLength > 0) {
658 this.buffer[this.bufferLength++] = data[dataPos++];
659 dataLength--;
660 }
661 return this;
662 }
663 // Finalizes hash state and puts hash into out.
664 //
665 // If hash was already finalized, puts the same value.
666 finish(out) {
667 if (!this.finished) {
668 const bytesHashed = this.bytesHashed;
669 const left = this.bufferLength;
670 const bitLenHi = (bytesHashed / 0x20000000) | 0;
671 const bitLenLo = bytesHashed << 3;
672 const padLength = bytesHashed % 64 < 56 ? 64 : 128;
673 this.buffer[left] = 0x80;
674 for (let i = left + 1; i < padLength - 8; i++) {
675 this.buffer[i] = 0;
676 }
677 this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff;
678 this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff;
679 this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff;
680 this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff;
681 this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff;
682 this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff;
683 this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff;
684 this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff;
685 hashBlocks(this.temp, this.state, this.buffer, 0, padLength);
686 this.finished = true;
687 }
688 for (let i = 0; i < 8; i++) {
689 out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff;
690 out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff;
691 out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff;
692 out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff;
693 }
694 return this;
695 }
696 // Returns the final hash digest.
697 digest() {
698 const out = new Uint8Array(this.digestLength);
699 this.finish(out);
700 return out;
701 }
702 // Internal function for use in HMAC for optimization.
703 _saveState(out) {
704 for (let i = 0; i < this.state.length; i++) {
705 out[i] = this.state[i];
706 }
707 }
708 // Internal function for use in HMAC for optimization.
709 _restoreState(from, bytesHashed) {
710 for (let i = 0; i < this.state.length; i++) {
711 this.state[i] = from[i];
712 }
713 this.bytesHashed = bytesHashed;
714 this.finished = false;
715 this.bufferLength = 0;
716 }
717}
718// HMAC implements HMAC-SHA256 message authentication algorithm.
719class HMAC {
720 constructor(key) {
721 this.inner = new Hash();
722 this.outer = new Hash();
723 this.blockSize = this.inner.blockSize;
724 this.digestLength = this.inner.digestLength;
725 const pad = new Uint8Array(this.blockSize);
726 if (key.length > this.blockSize) {
727 new Hash().update(key).finish(pad).clean();
728 }
729 else {
730 for (let i = 0; i < key.length; i++) {
731 pad[i] = key[i];
732 }
733 }
734 for (let i = 0; i < pad.length; i++) {
735 pad[i] ^= 0x36;
736 }
737 this.inner.update(pad);
738 for (let i = 0; i < pad.length; i++) {
739 pad[i] ^= 0x36 ^ 0x5c;
740 }
741 this.outer.update(pad);
742 this.istate = new Uint32Array(8);
743 this.ostate = new Uint32Array(8);
744 this.inner._saveState(this.istate);
745 this.outer._saveState(this.ostate);
746 for (let i = 0; i < pad.length; i++) {
747 pad[i] = 0;
748 }
749 }
750 // Returns HMAC state to the state initialized with key
751 // to make it possible to run HMAC over the other data with the same
752 // key without creating a new instance.
753 reset() {
754 this.inner._restoreState(this.istate, this.inner.blockSize);
755 this.outer._restoreState(this.ostate, this.outer.blockSize);
756 return this;
757 }
758 // Cleans HMAC state.
759 clean() {
760 for (let i = 0; i < this.istate.length; i++) {
761 this.ostate[i] = this.istate[i] = 0;
762 }
763 this.inner.clean();
764 this.outer.clean();
765 }
766 // Updates state with provided data.
767 update(data) {
768 this.inner.update(data);
769 return this;
770 }
771 // Finalizes HMAC and puts the result in out.
772 finish(out) {
773 if (this.outer.finished) {
774 this.outer.finish(out);
775 }
776 else {
777 this.inner.finish(out);
778 this.outer.update(out, this.digestLength).finish(out);
779 }
780 return this;
781 }
782 // Returns message authentication code.
783 digest() {
784 const out = new Uint8Array(this.digestLength);
785 this.finish(out);
786 return out;
787 }
788}
789// Returns SHA256 hash of data.
790function hash(data) {
791 const h = new Hash().update(data);
792 const digest = h.digest();
793 h.clean();
794 return digest;
795}
796// Returns HMAC-SHA256 of data under the key.
797function hmac(key, data) {
798 const h = new HMAC(key).update(data);
799 const digest = h.digest();
800 h.clean();
801 return digest;
802}
803// Fills hkdf buffer like this:
804// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
805function fillBuffer(buffer, hmac, info, counter) {
806 // Counter is a byte value: check if it overflowed.
807 const num = counter[0];
808 if (num === 0) {
809 throw new Error('hkdf: cannot expand more');
810 }
811 // Prepare HMAC instance for new data with old key.
812 hmac.reset();
813 // Hash in previous output if it was generated
814 // (i.e. counter is greater than 1).
815 if (num > 1) {
816 hmac.update(buffer);
817 }
818 // Hash in info if it exists.
819 if (info) {
820 hmac.update(info);
821 }
822 // Hash in the counter.
823 hmac.update(counter);
824 // Output result to buffer and clean HMAC instance.
825 hmac.finish(buffer);
826 // Increment counter inside typed array, this works properly.
827 counter[0]++;
828}
829const hkdfSalt = new Uint8Array(digestLength); // Filled with zeroes.
830function hkdf(key, salt = hkdfSalt, info, length = 32) {
831 const counter = new Uint8Array([1]);
832 // HKDF-Extract uses salt as HMAC key, and key as data.
833 const okm = hmac(salt, key);
834 // Initialize HMAC for expanding with extracted key.
835 // Ensure no collisions with `hmac` function.
836 const hmac_ = new HMAC(okm);
837 // Allocate buffer.
838 const buffer = new Uint8Array(hmac_.digestLength);
839 let bufpos = buffer.length;
840 const out = new Uint8Array(length);
841 for (let i = 0; i < length; i++) {
842 if (bufpos === buffer.length) {
843 fillBuffer(buffer, hmac_, info, counter);
844 bufpos = 0;
845 }
846 out[i] = buffer[bufpos++];
847 }
848 hmac_.clean();
849 buffer.fill(0);
850 counter.fill(0);
851 return out;
852}
853// Derives a key from password and salt using PBKDF2-HMAC-SHA256
854// with the given number of iterations.
855//
856// The number of bytes returned is equal to dkLen.
857//
858// (For better security, avoid dkLen greater than hash length - 32 bytes).
859function pbkdf2(password, salt, iterations, dkLen) {
860 const prf = new HMAC(password);
861 const len = prf.digestLength;
862 const ctr = new Uint8Array(4);
863 const t = new Uint8Array(len);
864 const u = new Uint8Array(len);
865 const dk = new Uint8Array(dkLen);
866 for (let i = 0; i * len < dkLen; i++) {
867 const c = i + 1;
868 ctr[0] = (c >>> 24) & 0xff;
869 ctr[1] = (c >>> 16) & 0xff;
870 ctr[2] = (c >>> 8) & 0xff;
871 ctr[3] = (c >>> 0) & 0xff;
872 prf.reset();
873 prf.update(salt);
874 prf.update(ctr);
875 prf.finish(u);
876 for (let j = 0; j < len; j++) {
877 t[j] = u[j];
878 }
879 for (let j = 2; j <= iterations; j++) {
880 prf.reset();
881 prf.update(u).finish(u);
882 for (let k = 0; k < len; k++) {
883 t[k] ^= u[k];
884 }
885 }
886 for (let j = 0; j < len && i * len + j < dkLen; j++) {
887 dk[i * len + j] = t[j];
888 }
889 }
890 for (let i = 0; i < len; i++) {
891 t[i] = u[i] = 0;
892 }
893 for (let i = 0; i < 4; i++) {
894 ctr[i] = 0;
895 }
896 prf.clean();
897 return dk;
898}
899
900/**
901 * Abstraction for crypto algorithms
902 */
903class HashHandler {
904}
905function decodeUTF8(s) {
906 if (typeof s !== 'string')
907 throw new TypeError('expected string');
908 const d = s, b = new Uint8Array(d.length);
909 for (let i = 0; i < d.length; i++)
910 b[i] = d.charCodeAt(i);
911 return b;
912}
913function encodeUTF8(arr) {
914 const s = [];
915 for (let i = 0; i < arr.length; i++)
916 s.push(String.fromCharCode(arr[i]));
917 return s.join('');
918}
919class DefaultHashHandler {
920 async calcHash(valueToHash, algorithm) {
921 // const encoder = new TextEncoder();
922 // const hashArray = await window.crypto.subtle.digest(algorithm, data);
923 // const data = encoder.encode(valueToHash);
924 // const fhash = fsha256(valueToHash);
925 const candHash = encodeUTF8(hash(decodeUTF8(valueToHash)));
926 // const hashArray = (sha256 as any).array(valueToHash);
927 // // const hashString = this.toHashString(hashArray);
928 // const hashString = this.toHashString2(hashArray);
929 // console.debug('hash orig - cand', candHash, hashString);
930 // alert(1);
931 return candHash;
932 }
933 toHashString2(byteArray) {
934 let result = '';
935 for (const e of byteArray) {
936 result += String.fromCharCode(e);
937 }
938 return result;
939 }
940 toHashString(buffer) {
941 const byteArray = new Uint8Array(buffer);
942 let result = '';
943 for (const e of byteArray) {
944 result += String.fromCharCode(e);
945 }
946 return result;
947 }
948 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultHashHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
949 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultHashHandler }); }
950}
951i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultHashHandler, decorators: [{
952 type: Injectable
953 }] });
954
955/**
956 * Service for logging in and logging out with
957 * OIDC and OAuth2. Supports implicit flow and
958 * password flow.
959 */
960class OAuthService extends AuthConfig {
961 constructor(ngZone, http, storage, tokenValidationHandler, config, urlHelper, logger, crypto, document, dateTimeService) {
962 super();
963 this.ngZone = ngZone;
964 this.http = http;
965 this.config = config;
966 this.urlHelper = urlHelper;
967 this.logger = logger;
968 this.crypto = crypto;
969 this.dateTimeService = dateTimeService;
970 /**
971 * @internal
972 * Deprecated: use property events instead
973 */
974 this.discoveryDocumentLoaded = false;
975 /**
976 * The received (passed around) state, when logging
977 * in with implicit flow.
978 */
979 this.state = '';
980 this.eventsSubject = new Subject();
981 this.discoveryDocumentLoadedSubject = new Subject();
982 this.grantTypesSupported = [];
983 this.inImplicitFlow = false;
984 this.saveNoncesInLocalStorage = false;
985 this.debug('angular-oauth2-oidc v10');
986 // See https://github.com/manfredsteyer/angular-oauth2-oidc/issues/773 for why this is needed
987 this.document = document;
988 if (!config) {
989 config = {};
990 }
991 this.discoveryDocumentLoaded$ =
992 this.discoveryDocumentLoadedSubject.asObservable();
993 this.events = this.eventsSubject.asObservable();
994 if (tokenValidationHandler) {
995 this.tokenValidationHandler = tokenValidationHandler;
996 }
997 if (config) {
998 this.configure(config);
999 }
1000 try {
1001 if (storage) {
1002 this.setStorage(storage);
1003 }
1004 else if (typeof sessionStorage !== 'undefined') {
1005 this.setStorage(sessionStorage);
1006 }
1007 }
1008 catch (e) {
1009 console.error('No OAuthStorage provided and cannot access default (sessionStorage).' +
1010 'Consider providing a custom OAuthStorage implementation in your module.', e);
1011 }
1012 // in IE, sessionStorage does not always survive a redirect
1013 if (this.checkLocalStorageAccessable()) {
1014 const ua = window?.navigator?.userAgent;
1015 const msie = ua?.includes('MSIE ') || ua?.includes('Trident');
1016 if (msie) {
1017 this.saveNoncesInLocalStorage = true;
1018 }
1019 }
1020 this.setupRefreshTimer();
1021 }
1022 checkLocalStorageAccessable() {
1023 if (typeof window === 'undefined')
1024 return false;
1025 const test = 'test';
1026 try {
1027 if (typeof window['localStorage'] === 'undefined')
1028 return false;
1029 localStorage.setItem(test, test);
1030 localStorage.removeItem(test);
1031 return true;
1032 }
1033 catch (e) {
1034 return false;
1035 }
1036 }
1037 /**
1038 * Use this method to configure the service
1039 * @param config the configuration
1040 */
1041 configure(config) {
1042 // For the sake of downward compatibility with
1043 // original configuration API
1044 Object.assign(this, new AuthConfig(), config);
1045 this.config = Object.assign({}, new AuthConfig(), config);
1046 if (this.sessionChecksEnabled) {
1047 this.setupSessionCheck();
1048 }
1049 this.configChanged();
1050 }
1051 configChanged() {
1052 this.setupRefreshTimer();
1053 }
1054 restartSessionChecksIfStillLoggedIn() {
1055 if (this.hasValidIdToken()) {
1056 this.initSessionCheck();
1057 }
1058 }
1059 restartRefreshTimerIfStillLoggedIn() {
1060 this.setupExpirationTimers();
1061 }
1062 setupSessionCheck() {
1063 this.events
1064 .pipe(filter((e) => e.type === 'token_received'))
1065 .subscribe(() => {
1066 this.initSessionCheck();
1067 });
1068 }
1069 /**
1070 * Will setup up silent refreshing for when the token is
1071 * about to expire. When the user is logged out via this.logOut method, the
1072 * silent refreshing will pause and not refresh the tokens until the user is
1073 * logged back in via receiving a new token.
1074 * @param params Additional parameter to pass
1075 * @param listenTo Setup automatic refresh of a specific token type
1076 */
1077 setupAutomaticSilentRefresh(params = {}, listenTo, noPrompt = true) {
1078 let shouldRunSilentRefresh = true;
1079 this.clearAutomaticRefreshTimer();
1080 this.automaticRefreshSubscription = this.events
1081 .pipe(tap((e) => {
1082 if (e.type === 'token_received') {
1083 shouldRunSilentRefresh = true;
1084 }
1085 else if (e.type === 'logout') {
1086 shouldRunSilentRefresh = false;
1087 }
1088 }), filter((e) => e.type === 'token_expires' &&
1089 (listenTo == null || listenTo === 'any' || e.info === listenTo)), debounceTime(1000))
1090 .subscribe(() => {
1091 if (shouldRunSilentRefresh) {
1092 // this.silentRefresh(params, noPrompt).catch(_ => {
1093 this.refreshInternal(params, noPrompt).catch(() => {
1094 this.debug('Automatic silent refresh did not work');
1095 });
1096 }
1097 });
1098 this.restartRefreshTimerIfStillLoggedIn();
1099 }
1100 refreshInternal(params, noPrompt) {
1101 if (!this.useSilentRefresh && this.responseType === 'code') {
1102 return this.refreshToken();
1103 }
1104 else {
1105 return this.silentRefresh(params, noPrompt);
1106 }
1107 }
1108 /**
1109 * Convenience method that first calls `loadDiscoveryDocument(...)` and
1110 * directly chains using the `then(...)` part of the promise to call
1111 * the `tryLogin(...)` method.
1112 *
1113 * @param options LoginOptions to pass through to `tryLogin(...)`
1114 */
1115 loadDiscoveryDocumentAndTryLogin(options = null) {
1116 return this.loadDiscoveryDocument().then(() => {
1117 return this.tryLogin(options);
1118 });
1119 }
1120 /**
1121 * Convenience method that first calls `loadDiscoveryDocumentAndTryLogin(...)`
1122 * and if then chains to `initLoginFlow()`, but only if there is no valid
1123 * IdToken or no valid AccessToken.
1124 *
1125 * @param options LoginOptions to pass through to `tryLogin(...)`
1126 */
1127 loadDiscoveryDocumentAndLogin(options = null) {
1128 options = options || {};
1129 return this.loadDiscoveryDocumentAndTryLogin(options).then(() => {
1130 if (!this.hasValidIdToken() || !this.hasValidAccessToken()) {
1131 const state = typeof options.state === 'string' ? options.state : '';
1132 this.initLoginFlow(state);
1133 return false;
1134 }
1135 else {
1136 return true;
1137 }
1138 });
1139 }
1140 debug(...args) {
1141 if (this.showDebugInformation) {
1142 this.logger.debug(...args);
1143 }
1144 }
1145 validateUrlFromDiscoveryDocument(url) {
1146 const errors = [];
1147 const httpsCheck = this.validateUrlForHttps(url);
1148 const issuerCheck = this.validateUrlAgainstIssuer(url);
1149 if (!httpsCheck) {
1150 errors.push('https for all urls required. Also for urls received by discovery.');
1151 }
1152 if (!issuerCheck) {
1153 errors.push('Every url in discovery document has to start with the issuer url.' +
1154 'Also see property strictDiscoveryDocumentValidation.');
1155 }
1156 return errors;
1157 }
1158 validateUrlForHttps(url) {
1159 if (!url) {
1160 return true;
1161 }
1162 const lcUrl = url.toLowerCase();
1163 if (this.requireHttps === false) {
1164 return true;
1165 }
1166 if ((lcUrl.match(/^http:\/\/localhost($|[:/])/) ||
1167 lcUrl.match(/^http:\/\/localhost($|[:/])/)) &&
1168 this.requireHttps === 'remoteOnly') {
1169 return true;
1170 }
1171 return lcUrl.startsWith('https://');
1172 }
1173 assertUrlNotNullAndCorrectProtocol(url, description) {
1174 if (!url) {
1175 throw new Error(`'${description}' should not be null`);
1176 }
1177 if (!this.validateUrlForHttps(url)) {
1178 throw new Error(`'${description}' must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).`);
1179 }
1180 }
1181 validateUrlAgainstIssuer(url) {
1182 if (!this.strictDiscoveryDocumentValidation) {
1183 return true;
1184 }
1185 if (!url) {
1186 return true;
1187 }
1188 return url.toLowerCase().startsWith(this.issuer.toLowerCase());
1189 }
1190 setupRefreshTimer() {
1191 if (typeof window === 'undefined') {
1192 this.debug('timer not supported on this plattform');
1193 return;
1194 }
1195 if (this.hasValidIdToken() || this.hasValidAccessToken()) {
1196 this.clearAccessTokenTimer();
1197 this.clearIdTokenTimer();
1198 this.setupExpirationTimers();
1199 }
1200 if (this.tokenReceivedSubscription)
1201 this.tokenReceivedSubscription.unsubscribe();
1202 this.tokenReceivedSubscription = this.events
1203 .pipe(filter((e) => e.type === 'token_received'))
1204 .subscribe(() => {
1205 this.clearAccessTokenTimer();
1206 this.clearIdTokenTimer();
1207 this.setupExpirationTimers();
1208 });
1209 }
1210 setupExpirationTimers() {
1211 if (this.hasValidAccessToken()) {
1212 this.setupAccessTokenTimer();
1213 }
1214 if (!this.disableIdTokenTimer && this.hasValidIdToken()) {
1215 this.setupIdTokenTimer();
1216 }
1217 }
1218 setupAccessTokenTimer() {
1219 const expiration = this.getAccessTokenExpiration();
1220 const storedAt = this.getAccessTokenStoredAt();
1221 const timeout = this.calcTimeout(storedAt, expiration);
1222 this.ngZone.runOutsideAngular(() => {
1223 this.accessTokenTimeoutSubscription = of(new OAuthInfoEvent('token_expires', 'access_token'))
1224 .pipe(delay(timeout))
1225 .subscribe((e) => {
1226 this.ngZone.run(() => {
1227 this.eventsSubject.next(e);
1228 });
1229 });
1230 });
1231 }
1232 setupIdTokenTimer() {
1233 const expiration = this.getIdTokenExpiration();
1234 const storedAt = this.getIdTokenStoredAt();
1235 const timeout = this.calcTimeout(storedAt, expiration);
1236 this.ngZone.runOutsideAngular(() => {
1237 this.idTokenTimeoutSubscription = of(new OAuthInfoEvent('token_expires', 'id_token'))
1238 .pipe(delay(timeout))
1239 .subscribe((e) => {
1240 this.ngZone.run(() => {
1241 this.eventsSubject.next(e);
1242 });
1243 });
1244 });
1245 }
1246 /**
1247 * Stops timers for automatic refresh.
1248 * To restart it, call setupAutomaticSilentRefresh again.
1249 */
1250 stopAutomaticRefresh() {
1251 this.clearAccessTokenTimer();
1252 this.clearIdTokenTimer();
1253 this.clearAutomaticRefreshTimer();
1254 }
1255 clearAccessTokenTimer() {
1256 if (this.accessTokenTimeoutSubscription) {
1257 this.accessTokenTimeoutSubscription.unsubscribe();
1258 }
1259 }
1260 clearIdTokenTimer() {
1261 if (this.idTokenTimeoutSubscription) {
1262 this.idTokenTimeoutSubscription.unsubscribe();
1263 }
1264 }
1265 clearAutomaticRefreshTimer() {
1266 if (this.automaticRefreshSubscription) {
1267 this.automaticRefreshSubscription.unsubscribe();
1268 }
1269 }
1270 calcTimeout(storedAt, expiration) {
1271 const now = this.dateTimeService.now();
1272 const delta = (expiration - storedAt) * this.timeoutFactor - (now - storedAt);
1273 const duration = Math.max(0, delta);
1274 const maxTimeoutValue = 2147483647;
1275 return duration > maxTimeoutValue ? maxTimeoutValue : duration;
1276 }
1277 /**
1278 * DEPRECATED. Use a provider for OAuthStorage instead:
1279 *
1280 * { provide: OAuthStorage, useFactory: oAuthStorageFactory }
1281 * export function oAuthStorageFactory(): OAuthStorage { return localStorage; }
1282 * Sets a custom storage used to store the received
1283 * tokens on client side. By default, the browser's
1284 * sessionStorage is used.
1285 * @ignore
1286 *
1287 * @param storage
1288 */
1289 setStorage(storage) {
1290 this._storage = storage;
1291 this.configChanged();
1292 }
1293 /**
1294 * Loads the discovery document to configure most
1295 * properties of this service. The url of the discovery
1296 * document is infered from the issuer's url according
1297 * to the OpenId Connect spec. To use another url you
1298 * can pass it to to optional parameter fullUrl.
1299 *
1300 * @param fullUrl
1301 */
1302 loadDiscoveryDocument(fullUrl = null) {
1303 return new Promise((resolve, reject) => {
1304 if (!fullUrl) {
1305 fullUrl = this.issuer || '';
1306 if (!fullUrl.endsWith('/')) {
1307 fullUrl += '/';
1308 }
1309 fullUrl += '.well-known/openid-configuration';
1310 }
1311 if (!this.validateUrlForHttps(fullUrl)) {
1312 reject("issuer must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
1313 return;
1314 }
1315 this.http.get(fullUrl).subscribe((doc) => {
1316 if (!this.validateDiscoveryDocument(doc)) {
1317 this.eventsSubject.next(new OAuthErrorEvent('discovery_document_validation_error', null));
1318 reject('discovery_document_validation_error');
1319 return;
1320 }
1321 this.loginUrl = doc.authorization_endpoint;
1322 this.logoutUrl = doc.end_session_endpoint || this.logoutUrl;
1323 this.grantTypesSupported = doc.grant_types_supported;
1324 this.issuer = doc.issuer;
1325 this.tokenEndpoint = doc.token_endpoint;
1326 this.userinfoEndpoint =
1327 doc.userinfo_endpoint || this.userinfoEndpoint;
1328 this.jwksUri = doc.jwks_uri;
1329 this.sessionCheckIFrameUrl =
1330 doc.check_session_iframe || this.sessionCheckIFrameUrl;
1331 this.discoveryDocumentLoaded = true;
1332 this.discoveryDocumentLoadedSubject.next(doc);
1333 this.revocationEndpoint =
1334 doc.revocation_endpoint || this.revocationEndpoint;
1335 if (this.sessionChecksEnabled) {
1336 this.restartSessionChecksIfStillLoggedIn();
1337 }
1338 this.loadJwks()
1339 .then((jwks) => {
1340 const result = {
1341 discoveryDocument: doc,
1342 jwks: jwks,
1343 };
1344 const event = new OAuthSuccessEvent('discovery_document_loaded', result);
1345 this.eventsSubject.next(event);
1346 resolve(event);
1347 return;
1348 })
1349 .catch((err) => {
1350 this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err));
1351 reject(err);
1352 return;
1353 });
1354 }, (err) => {
1355 this.logger.error('error loading discovery document', err);
1356 this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err));
1357 reject(err);
1358 });
1359 });
1360 }
1361 loadJwks() {
1362 return new Promise((resolve, reject) => {
1363 if (this.jwksUri) {
1364 this.http.get(this.jwksUri).subscribe((jwks) => {
1365 this.jwks = jwks;
1366 // this.eventsSubject.next(
1367 // new OAuthSuccessEvent('discovery_document_loaded')
1368 // );
1369 resolve(jwks);
1370 }, (err) => {
1371 this.logger.error('error loading jwks', err);
1372 this.eventsSubject.next(new OAuthErrorEvent('jwks_load_error', err));
1373 reject(err);
1374 });
1375 }
1376 else {
1377 resolve(null);
1378 }
1379 });
1380 }
1381 validateDiscoveryDocument(doc) {
1382 let errors;
1383 if (!this.skipIssuerCheck && doc.issuer !== this.issuer) {
1384 this.logger.error('invalid issuer in discovery document', 'expected: ' + this.issuer, 'current: ' + doc.issuer);
1385 return false;
1386 }
1387 errors = this.validateUrlFromDiscoveryDocument(doc.authorization_endpoint);
1388 if (errors.length > 0) {
1389 this.logger.error('error validating authorization_endpoint in discovery document', errors);
1390 return false;
1391 }
1392 errors = this.validateUrlFromDiscoveryDocument(doc.end_session_endpoint);
1393 if (errors.length > 0) {
1394 this.logger.error('error validating end_session_endpoint in discovery document', errors);
1395 return false;
1396 }
1397 errors = this.validateUrlFromDiscoveryDocument(doc.token_endpoint);
1398 if (errors.length > 0) {
1399 this.logger.error('error validating token_endpoint in discovery document', errors);
1400 }
1401 errors = this.validateUrlFromDiscoveryDocument(doc.revocation_endpoint);
1402 if (errors.length > 0) {
1403 this.logger.error('error validating revocation_endpoint in discovery document', errors);
1404 }
1405 errors = this.validateUrlFromDiscoveryDocument(doc.userinfo_endpoint);
1406 if (errors.length > 0) {
1407 this.logger.error('error validating userinfo_endpoint in discovery document', errors);
1408 return false;
1409 }
1410 errors = this.validateUrlFromDiscoveryDocument(doc.jwks_uri);
1411 if (errors.length > 0) {
1412 this.logger.error('error validating jwks_uri in discovery document', errors);
1413 return false;
1414 }
1415 if (this.sessionChecksEnabled && !doc.check_session_iframe) {
1416 this.logger.warn('sessionChecksEnabled is activated but discovery document' +
1417 ' does not contain a check_session_iframe field');
1418 }
1419 return true;
1420 }
1421 /**
1422 * Uses password flow to exchange userName and password for an
1423 * access_token. After receiving the access_token, this method
1424 * uses it to query the userinfo endpoint in order to get information
1425 * about the user in question.
1426 *
1427 * When using this, make sure that the property oidc is set to false.
1428 * Otherwise stricter validations take place that make this operation
1429 * fail.
1430 *
1431 * @param userName
1432 * @param password
1433 * @param headers Optional additional http-headers.
1434 */
1435 fetchTokenUsingPasswordFlowAndLoadUserProfile(userName, password, headers = new HttpHeaders()) {
1436 return this.fetchTokenUsingPasswordFlow(userName, password, headers).then(() => this.loadUserProfile());
1437 }
1438 /**
1439 * Loads the user profile by accessing the user info endpoint defined by OpenId Connect.
1440 *
1441 * When using this with OAuth2 password flow, make sure that the property oidc is set to false.
1442 * Otherwise stricter validations take place that make this operation fail.
1443 */
1444 loadUserProfile() {
1445 if (!this.hasValidAccessToken()) {
1446 throw new Error('Can not load User Profile without access_token');
1447 }
1448 if (!this.validateUrlForHttps(this.userinfoEndpoint)) {
1449 throw new Error("userinfoEndpoint must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
1450 }
1451 return new Promise((resolve, reject) => {
1452 const headers = new HttpHeaders().set('Authorization', 'Bearer ' + this.getAccessToken());
1453 this.http
1454 .get(this.userinfoEndpoint, {
1455 headers,
1456 observe: 'response',
1457 responseType: 'text',
1458 })
1459 .subscribe((response) => {
1460 this.debug('userinfo received', JSON.stringify(response));
1461 if (response.headers
1462 .get('content-type')
1463 .startsWith('application/json')) {
1464 let info = JSON.parse(response.body);
1465 const existingClaims = this.getIdentityClaims() || {};
1466 if (!this.skipSubjectCheck) {
1467 if (this.oidc &&
1468 (!existingClaims['sub'] || info.sub !== existingClaims['sub'])) {
1469 const err = 'if property oidc is true, the received user-id (sub) has to be the user-id ' +
1470 'of the user that has logged in with oidc.\n' +
1471 'if you are not using oidc but just oauth2 password flow set oidc to false';
1472 reject(err);
1473 return;
1474 }
1475 }
1476 info = Object.assign({}, existingClaims, info);
1477 this._storage.setItem('id_token_claims_obj', JSON.stringify(info));
1478 this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded'));
1479 resolve({ info });
1480 }
1481 else {
1482 this.debug('userinfo is not JSON, treating it as JWE/JWS');
1483 this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded'));
1484 resolve(JSON.parse(response.body));
1485 }
1486 }, (err) => {
1487 this.logger.error('error loading user info', err);
1488 this.eventsSubject.next(new OAuthErrorEvent('user_profile_load_error', err));
1489 reject(err);
1490 });
1491 });
1492 }
1493 /**
1494 * Uses password flow to exchange userName and password for an access_token.
1495 * @param userName
1496 * @param password
1497 * @param headers Optional additional http-headers.
1498 */
1499 fetchTokenUsingPasswordFlow(userName, password, headers = new HttpHeaders()) {
1500 const parameters = {
1501 username: userName,
1502 password: password,
1503 };
1504 return this.fetchTokenUsingGrant('password', parameters, headers);
1505 }
1506 /**
1507 * Uses a custom grant type to retrieve tokens.
1508 * @param grantType Grant type.
1509 * @param parameters Parameters to pass.
1510 * @param headers Optional additional HTTP headers.
1511 */
1512 fetchTokenUsingGrant(grantType, parameters, headers = new HttpHeaders()) {
1513 this.assertUrlNotNullAndCorrectProtocol(this.tokenEndpoint, 'tokenEndpoint');
1514 /**
1515 * A `HttpParameterCodec` that uses `encodeURIComponent` and `decodeURIComponent` to
1516 * serialize and parse URL parameter keys and values.
1517 *
1518 * @stable
1519 */
1520 let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
1521 .set('grant_type', grantType)
1522 .set('scope', this.scope);
1523 if (this.useHttpBasicAuth) {
1524 const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
1525 headers = headers.set('Authorization', 'Basic ' + header);
1526 }
1527 if (!this.useHttpBasicAuth) {
1528 params = params.set('client_id', this.clientId);
1529 }
1530 if (!this.useHttpBasicAuth && this.dummyClientSecret) {
1531 params = params.set('client_secret', this.dummyClientSecret);
1532 }
1533 if (this.customQueryParams) {
1534 for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
1535 params = params.set(key, this.customQueryParams[key]);
1536 }
1537 }
1538 // set explicit parameters last, to allow overwriting
1539 for (const key of Object.keys(parameters)) {
1540 params = params.set(key, parameters[key]);
1541 }
1542 headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
1543 return new Promise((resolve, reject) => {
1544 this.http
1545 .post(this.tokenEndpoint, params, { headers })
1546 .subscribe((tokenResponse) => {
1547 this.debug('tokenResponse', tokenResponse);
1548 this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in ||
1549 this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse));
1550 if (this.oidc && tokenResponse.id_token) {
1551 this.processIdToken(tokenResponse.id_token, tokenResponse.access_token).then((result) => {
1552 this.storeIdToken(result);
1553 resolve(tokenResponse);
1554 });
1555 }
1556 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
1557 resolve(tokenResponse);
1558 }, (err) => {
1559 this.logger.error('Error performing ${grantType} flow', err);
1560 this.eventsSubject.next(new OAuthErrorEvent('token_error', err));
1561 reject(err);
1562 });
1563 });
1564 }
1565 /**
1566 * Refreshes the token using a refresh_token.
1567 * This does not work for implicit flow, b/c
1568 * there is no refresh_token in this flow.
1569 * A solution for this is provided by the
1570 * method silentRefresh.
1571 */
1572 refreshToken() {
1573 this.assertUrlNotNullAndCorrectProtocol(this.tokenEndpoint, 'tokenEndpoint');
1574 return new Promise((resolve, reject) => {
1575 let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
1576 .set('grant_type', 'refresh_token')
1577 .set('scope', this.scope)
1578 .set('refresh_token', this._storage.getItem('refresh_token'));
1579 let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
1580 if (this.useHttpBasicAuth) {
1581 const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
1582 headers = headers.set('Authorization', 'Basic ' + header);
1583 }
1584 if (!this.useHttpBasicAuth) {
1585 params = params.set('client_id', this.clientId);
1586 }
1587 if (!this.useHttpBasicAuth && this.dummyClientSecret) {
1588 params = params.set('client_secret', this.dummyClientSecret);
1589 }
1590 if (this.customQueryParams) {
1591 for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
1592 params = params.set(key, this.customQueryParams[key]);
1593 }
1594 }
1595 this.http
1596 .post(this.tokenEndpoint, params, { headers })
1597 .pipe(switchMap((tokenResponse) => {
1598 if (this.oidc && tokenResponse.id_token) {
1599 return from(this.processIdToken(tokenResponse.id_token, tokenResponse.access_token, true)).pipe(tap((result) => this.storeIdToken(result)), map(() => tokenResponse));
1600 }
1601 else {
1602 return of(tokenResponse);
1603 }
1604 }))
1605 .subscribe((tokenResponse) => {
1606 this.debug('refresh tokenResponse', tokenResponse);
1607 this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in ||
1608 this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse));
1609 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
1610 this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
1611 resolve(tokenResponse);
1612 }, (err) => {
1613 this.logger.error('Error refreshing token', err);
1614 this.eventsSubject.next(new OAuthErrorEvent('token_refresh_error', err));
1615 reject(err);
1616 });
1617 });
1618 }
1619 removeSilentRefreshEventListener() {
1620 if (this.silentRefreshPostMessageEventListener) {
1621 window.removeEventListener('message', this.silentRefreshPostMessageEventListener);
1622 this.silentRefreshPostMessageEventListener = null;
1623 }
1624 }
1625 setupSilentRefreshEventListener() {
1626 this.removeSilentRefreshEventListener();
1627 this.silentRefreshPostMessageEventListener = (e) => {
1628 const message = this.processMessageEventMessage(e);
1629 if (this.checkOrigin && e.origin !== location.origin) {
1630 console.error('wrong origin requested silent refresh!');
1631 }
1632 this.tryLogin({
1633 customHashFragment: message,
1634 preventClearHashAfterLogin: true,
1635 customRedirectUri: this.silentRefreshRedirectUri || this.redirectUri,
1636 }).catch((err) => this.debug('tryLogin during silent refresh failed', err));
1637 };
1638 window.addEventListener('message', this.silentRefreshPostMessageEventListener);
1639 }
1640 /**
1641 * Performs a silent refresh for implicit flow.
1642 * Use this method to get new tokens when/before
1643 * the existing tokens expire.
1644 */
1645 silentRefresh(params = {}, noPrompt = true) {
1646 const claims = this.getIdentityClaims() || {};
1647 if (this.useIdTokenHintForSilentRefresh && this.hasValidIdToken()) {
1648 params['id_token_hint'] = this.getIdToken();
1649 }
1650 if (!this.validateUrlForHttps(this.loginUrl)) {
1651 throw new Error("loginUrl must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
1652 }
1653 if (typeof this.document === 'undefined') {
1654 throw new Error('silent refresh is not supported on this platform');
1655 }
1656 const existingIframe = this.document.getElementById(this.silentRefreshIFrameName);
1657 if (existingIframe) {
1658 this.document.body.removeChild(existingIframe);
1659 }
1660 this.silentRefreshSubject = claims['sub'];
1661 const iframe = this.document.createElement('iframe');
1662 iframe.id = this.silentRefreshIFrameName;
1663 this.setupSilentRefreshEventListener();
1664 const redirectUri = this.silentRefreshRedirectUri || this.redirectUri;
1665 this.createLoginUrl(null, null, redirectUri, noPrompt, params).then((url) => {
1666 iframe.setAttribute('src', url);
1667 if (!this.silentRefreshShowIFrame) {
1668 iframe.style['display'] = 'none';
1669 }
1670 this.document.body.appendChild(iframe);
1671 });
1672 const errors = this.events.pipe(filter((e) => e instanceof OAuthErrorEvent), first());
1673 const success = this.events.pipe(filter((e) => e.type === 'token_received'), first());
1674 const timeout = of(new OAuthErrorEvent('silent_refresh_timeout', null)).pipe(delay(this.silentRefreshTimeout));
1675 return race([errors, success, timeout])
1676 .pipe(map((e) => {
1677 if (e instanceof OAuthErrorEvent) {
1678 if (e.type === 'silent_refresh_timeout') {
1679 this.eventsSubject.next(e);
1680 }
1681 else {
1682 e = new OAuthErrorEvent('silent_refresh_error', e);
1683 this.eventsSubject.next(e);
1684 }
1685 throw e;
1686 }
1687 else if (e.type === 'token_received') {
1688 e = new OAuthSuccessEvent('silently_refreshed');
1689 this.eventsSubject.next(e);
1690 }
1691 return e;
1692 }))
1693 .toPromise();
1694 }
1695 /**
1696 * This method exists for backwards compatibility.
1697 * {@link OAuthService#initLoginFlowInPopup} handles both code
1698 * and implicit flows.
1699 */
1700 initImplicitFlowInPopup(options) {
1701 return this.initLoginFlowInPopup(options);
1702 }
1703 initLoginFlowInPopup(options) {
1704 options = options || {};
1705 return this.createLoginUrl(null, null, this.silentRefreshRedirectUri, false, {
1706 display: 'popup',
1707 }).then((url) => {
1708 return new Promise((resolve, reject) => {
1709 /**
1710 * Error handling section
1711 */
1712 const checkForPopupClosedInterval = 500;
1713 let windowRef = null;
1714 // If we got no window reference we open a window
1715 // else we are using the window already opened
1716 if (!options.windowRef) {
1717 windowRef = window.open(url, 'ngx-oauth2-oidc-login', this.calculatePopupFeatures(options));
1718 }
1719 else if (options.windowRef && !options.windowRef.closed) {
1720 windowRef = options.windowRef;
1721 windowRef.location.href = url;
1722 }
1723 let checkForPopupClosedTimer;
1724 const tryLogin = (hash) => {
1725 this.tryLogin({
1726 customHashFragment: hash,
1727 preventClearHashAfterLogin: true,
1728 customRedirectUri: this.silentRefreshRedirectUri,
1729 }).then(() => {
1730 cleanup();
1731 resolve(true);
1732 }, (err) => {
1733 cleanup();
1734 reject(err);
1735 });
1736 };
1737 const checkForPopupClosed = () => {
1738 if (!windowRef || windowRef.closed) {
1739 cleanup();
1740 reject(new OAuthErrorEvent('popup_closed', {}));
1741 }
1742 };
1743 if (!windowRef) {
1744 reject(new OAuthErrorEvent('popup_blocked', {}));
1745 }
1746 else {
1747 checkForPopupClosedTimer = window.setInterval(checkForPopupClosed, checkForPopupClosedInterval);
1748 }
1749 const cleanup = () => {
1750 window.clearInterval(checkForPopupClosedTimer);
1751 window.removeEventListener('storage', storageListener);
1752 window.removeEventListener('message', listener);
1753 if (windowRef !== null) {
1754 windowRef.close();
1755 }
1756 windowRef = null;
1757 };
1758 const listener = (e) => {
1759 const message = this.processMessageEventMessage(e);
1760 if (message && message !== null) {
1761 window.removeEventListener('storage', storageListener);
1762 tryLogin(message);
1763 }
1764 else {
1765 console.log('false event firing');
1766 }
1767 };
1768 const storageListener = (event) => {
1769 if (event.key === 'auth_hash') {
1770 window.removeEventListener('message', listener);
1771 tryLogin(event.newValue);
1772 }
1773 };
1774 window.addEventListener('message', listener);
1775 window.addEventListener('storage', storageListener);
1776 });
1777 });
1778 }
1779 calculatePopupFeatures(options) {
1780 // Specify an static height and width and calculate centered position
1781 const height = options.height || 470;
1782 const width = options.width || 500;
1783 const left = window.screenLeft + (window.outerWidth - width) / 2;
1784 const top = window.screenTop + (window.outerHeight - height) / 2;
1785 return `location=no,toolbar=no,width=${width},height=${height},top=${top},left=${left}`;
1786 }
1787 processMessageEventMessage(e) {
1788 let expectedPrefix = '#';
1789 if (this.silentRefreshMessagePrefix) {
1790 expectedPrefix += this.silentRefreshMessagePrefix;
1791 }
1792 if (!e || !e.data || typeof e.data !== 'string') {
1793 return;
1794 }
1795 const prefixedMessage = e.data;
1796 if (!prefixedMessage.startsWith(expectedPrefix)) {
1797 return;
1798 }
1799 return '#' + prefixedMessage.substr(expectedPrefix.length);
1800 }
1801 canPerformSessionCheck() {
1802 if (!this.sessionChecksEnabled) {
1803 return false;
1804 }
1805 if (!this.sessionCheckIFrameUrl) {
1806 console.warn('sessionChecksEnabled is activated but there is no sessionCheckIFrameUrl');
1807 return false;
1808 }
1809 const sessionState = this.getSessionState();
1810 if (!sessionState) {
1811 console.warn('sessionChecksEnabled is activated but there is no session_state');
1812 return false;
1813 }
1814 if (typeof this.document === 'undefined') {
1815 return false;
1816 }
1817 return true;
1818 }
1819 setupSessionCheckEventListener() {
1820 this.removeSessionCheckEventListener();
1821 this.sessionCheckEventListener = (e) => {
1822 const origin = e.origin.toLowerCase();
1823 const issuer = this.issuer.toLowerCase();
1824 this.debug('sessionCheckEventListener');
1825 if (!issuer.startsWith(origin)) {
1826 this.debug('sessionCheckEventListener', 'wrong origin', origin, 'expected', issuer, 'event', e);
1827 return;
1828 }
1829 // only run in Angular zone if it is 'changed' or 'error'
1830 switch (e.data) {
1831 case 'unchanged':
1832 this.ngZone.run(() => {
1833 this.handleSessionUnchanged();
1834 });
1835 break;
1836 case 'changed':
1837 this.ngZone.run(() => {
1838 this.handleSessionChange();
1839 });
1840 break;
1841 case 'error':
1842 this.ngZone.run(() => {
1843 this.handleSessionError();
1844 });
1845 break;
1846 }
1847 this.debug('got info from session check inframe', e);
1848 };
1849 // prevent Angular from refreshing the view on every message (runs in intervals)
1850 this.ngZone.runOutsideAngular(() => {
1851 window.addEventListener('message', this.sessionCheckEventListener);
1852 });
1853 }
1854 handleSessionUnchanged() {
1855 this.debug('session check', 'session unchanged');
1856 this.eventsSubject.next(new OAuthInfoEvent('session_unchanged'));
1857 }
1858 handleSessionChange() {
1859 this.eventsSubject.next(new OAuthInfoEvent('session_changed'));
1860 this.stopSessionCheckTimer();
1861 if (!this.useSilentRefresh && this.responseType === 'code') {
1862 this.refreshToken()
1863 .then(() => {
1864 this.debug('token refresh after session change worked');
1865 })
1866 .catch(() => {
1867 this.debug('token refresh did not work after session changed');
1868 this.eventsSubject.next(new OAuthInfoEvent('session_terminated'));
1869 this.logOut(true);
1870 });
1871 }
1872 else if (this.silentRefreshRedirectUri) {
1873 this.silentRefresh().catch(() => this.debug('silent refresh failed after session changed'));
1874 this.waitForSilentRefreshAfterSessionChange();
1875 }
1876 else {
1877 this.eventsSubject.next(new OAuthInfoEvent('session_terminated'));
1878 this.logOut(true);
1879 }
1880 }
1881 waitForSilentRefreshAfterSessionChange() {
1882 this.events
1883 .pipe(filter((e) => e.type === 'silently_refreshed' ||
1884 e.type === 'silent_refresh_timeout' ||
1885 e.type === 'silent_refresh_error'), first())
1886 .subscribe((e) => {
1887 if (e.type !== 'silently_refreshed') {
1888 this.debug('silent refresh did not work after session changed');
1889 this.eventsSubject.next(new OAuthInfoEvent('session_terminated'));
1890 this.logOut(true);
1891 }
1892 });
1893 }
1894 handleSessionError() {
1895 this.stopSessionCheckTimer();
1896 this.eventsSubject.next(new OAuthInfoEvent('session_error'));
1897 }
1898 removeSessionCheckEventListener() {
1899 if (this.sessionCheckEventListener) {
1900 window.removeEventListener('message', this.sessionCheckEventListener);
1901 this.sessionCheckEventListener = null;
1902 }
1903 }
1904 initSessionCheck() {
1905 if (!this.canPerformSessionCheck()) {
1906 return;
1907 }
1908 const existingIframe = this.document.getElementById(this.sessionCheckIFrameName);
1909 if (existingIframe) {
1910 this.document.body.removeChild(existingIframe);
1911 }
1912 const iframe = this.document.createElement('iframe');
1913 iframe.id = this.sessionCheckIFrameName;
1914 this.setupSessionCheckEventListener();
1915 const url = this.sessionCheckIFrameUrl;
1916 iframe.setAttribute('src', url);
1917 iframe.style.display = 'none';
1918 this.document.body.appendChild(iframe);
1919 this.startSessionCheckTimer();
1920 }
1921 startSessionCheckTimer() {
1922 this.stopSessionCheckTimer();
1923 this.ngZone.runOutsideAngular(() => {
1924 this.sessionCheckTimer = setInterval(this.checkSession.bind(this), this.sessionCheckIntervall);
1925 });
1926 }
1927 stopSessionCheckTimer() {
1928 if (this.sessionCheckTimer) {
1929 clearInterval(this.sessionCheckTimer);
1930 this.sessionCheckTimer = null;
1931 }
1932 }
1933 checkSession() {
1934 const iframe = this.document.getElementById(this.sessionCheckIFrameName);
1935 if (!iframe) {
1936 this.logger.warn('checkSession did not find iframe', this.sessionCheckIFrameName);
1937 }
1938 const sessionState = this.getSessionState();
1939 if (!sessionState) {
1940 this.stopSessionCheckTimer();
1941 }
1942 const message = this.clientId + ' ' + sessionState;
1943 iframe.contentWindow.postMessage(message, this.issuer);
1944 }
1945 async createLoginUrl(state = '', loginHint = '', customRedirectUri = '', noPrompt = false, params = {}) {
1946 const that = this; // eslint-disable-line @typescript-eslint/no-this-alias
1947 let redirectUri;
1948 if (customRedirectUri) {
1949 redirectUri = customRedirectUri;
1950 }
1951 else {
1952 redirectUri = this.redirectUri;
1953 }
1954 const nonce = await this.createAndSaveNonce();
1955 if (state) {
1956 state =
1957 nonce + this.config.nonceStateSeparator + encodeURIComponent(state);
1958 }
1959 else {
1960 state = nonce;
1961 }
1962 if (!this.requestAccessToken && !this.oidc) {
1963 throw new Error('Either requestAccessToken or oidc or both must be true');
1964 }
1965 if (this.config.responseType) {
1966 this.responseType = this.config.responseType;
1967 }
1968 else {
1969 if (this.oidc && this.requestAccessToken) {
1970 this.responseType = 'id_token token';
1971 }
1972 else if (this.oidc && !this.requestAccessToken) {
1973 this.responseType = 'id_token';
1974 }
1975 else {
1976 this.responseType = 'token';
1977 }
1978 }
1979 const seperationChar = that.loginUrl.indexOf('?') > -1 ? '&' : '?';
1980 let scope = that.scope;
1981 if (this.oidc && !scope.match(/(^|\s)openid($|\s)/)) {
1982 scope = 'openid ' + scope;
1983 }
1984 let url = that.loginUrl +
1985 seperationChar +
1986 'response_type=' +
1987 encodeURIComponent(that.responseType) +
1988 '&client_id=' +
1989 encodeURIComponent(that.clientId) +
1990 '&state=' +
1991 encodeURIComponent(state) +
1992 '&redirect_uri=' +
1993 encodeURIComponent(redirectUri) +
1994 '&scope=' +
1995 encodeURIComponent(scope);
1996 if (this.responseType.includes('code') && !this.disablePKCE) {
1997 const [challenge, verifier] = await this.createChallangeVerifierPairForPKCE();
1998 if (this.saveNoncesInLocalStorage &&
1999 typeof window['localStorage'] !== 'undefined') {
2000 localStorage.setItem('PKCE_verifier', verifier);
2001 }
2002 else {
2003 this._storage.setItem('PKCE_verifier', verifier);
2004 }
2005 url += '&code_challenge=' + challenge;
2006 url += '&code_challenge_method=S256';
2007 }
2008 if (loginHint) {
2009 url += '&login_hint=' + encodeURIComponent(loginHint);
2010 }
2011 if (that.resource) {
2012 url += '&resource=' + encodeURIComponent(that.resource);
2013 }
2014 if (that.oidc) {
2015 url += '&nonce=' + encodeURIComponent(nonce);
2016 }
2017 if (noPrompt) {
2018 url += '&prompt=none';
2019 }
2020 for (const key of Object.keys(params)) {
2021 url +=
2022 '&' + encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
2023 }
2024 if (this.customQueryParams) {
2025 for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
2026 url +=
2027 '&' + key + '=' + encodeURIComponent(this.customQueryParams[key]);
2028 }
2029 }
2030 return url;
2031 }
2032 initImplicitFlowInternal(additionalState = '', params = '') {
2033 if (this.inImplicitFlow) {
2034 return;
2035 }
2036 this.inImplicitFlow = true;
2037 if (!this.validateUrlForHttps(this.loginUrl)) {
2038 throw new Error("loginUrl must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
2039 }
2040 let addParams = {};
2041 let loginHint = null;
2042 if (typeof params === 'string') {
2043 loginHint = params;
2044 }
2045 else if (typeof params === 'object') {
2046 addParams = params;
2047 }
2048 this.createLoginUrl(additionalState, loginHint, null, false, addParams)
2049 .then(this.config.openUri)
2050 .catch((error) => {
2051 console.error('Error in initImplicitFlow', error);
2052 this.inImplicitFlow = false;
2053 });
2054 }
2055 /**
2056 * Starts the implicit flow and redirects to user to
2057 * the auth servers' login url.
2058 *
2059 * @param additionalState Optional state that is passed around.
2060 * You'll find this state in the property `state` after `tryLogin` logged in the user.
2061 * @param params Hash with additional parameter. If it is a string, it is used for the
2062 * parameter loginHint (for the sake of compatibility with former versions)
2063 */
2064 initImplicitFlow(additionalState = '', params = '') {
2065 if (this.loginUrl !== '') {
2066 this.initImplicitFlowInternal(additionalState, params);
2067 }
2068 else {
2069 this.events
2070 .pipe(filter((e) => e.type === 'discovery_document_loaded'))
2071 .subscribe(() => this.initImplicitFlowInternal(additionalState, params));
2072 }
2073 }
2074 /**
2075 * Reset current implicit flow
2076 *
2077 * @description This method allows resetting the current implict flow in order to be initialized again.
2078 */
2079 resetImplicitFlow() {
2080 this.inImplicitFlow = false;
2081 }
2082 callOnTokenReceivedIfExists(options) {
2083 const that = this; // eslint-disable-line @typescript-eslint/no-this-alias
2084 if (options.onTokenReceived) {
2085 const tokenParams = {
2086 idClaims: that.getIdentityClaims(),
2087 idToken: that.getIdToken(),
2088 accessToken: that.getAccessToken(),
2089 state: that.state,
2090 };
2091 options.onTokenReceived(tokenParams);
2092 }
2093 }
2094 storeAccessTokenResponse(accessToken, refreshToken, expiresIn, grantedScopes, customParameters) {
2095 this._storage.setItem('access_token', accessToken);
2096 if (grantedScopes && !Array.isArray(grantedScopes)) {
2097 this._storage.setItem('granted_scopes', JSON.stringify(grantedScopes.split(' ')));
2098 }
2099 else if (grantedScopes && Array.isArray(grantedScopes)) {
2100 this._storage.setItem('granted_scopes', JSON.stringify(grantedScopes));
2101 }
2102 this._storage.setItem('access_token_stored_at', '' + this.dateTimeService.now());
2103 if (expiresIn) {
2104 const expiresInMilliSeconds = expiresIn * 1000;
2105 const now = this.dateTimeService.new();
2106 const expiresAt = now.getTime() + expiresInMilliSeconds;
2107 this._storage.setItem('expires_at', '' + expiresAt);
2108 }
2109 if (refreshToken) {
2110 this._storage.setItem('refresh_token', refreshToken);
2111 }
2112 if (customParameters) {
2113 customParameters.forEach((value, key) => {
2114 this._storage.setItem(key, value);
2115 });
2116 }
2117 }
2118 /**
2119 * Delegates to tryLoginImplicitFlow for the sake of competability
2120 * @param options Optional options.
2121 */
2122 tryLogin(options = null) {
2123 if (this.config.responseType === 'code') {
2124 return this.tryLoginCodeFlow(options).then(() => true);
2125 }
2126 else {
2127 return this.tryLoginImplicitFlow(options);
2128 }
2129 }
2130 parseQueryString(queryString) {
2131 if (!queryString || queryString.length === 0) {
2132 return {};
2133 }
2134 if (queryString.charAt(0) === '?') {
2135 queryString = queryString.substr(1);
2136 }
2137 return this.urlHelper.parseQueryString(queryString);
2138 }
2139 async tryLoginCodeFlow(options = null) {
2140 options = options || {};
2141 const querySource = options.customHashFragment
2142 ? options.customHashFragment.substring(1)
2143 : window.location.search;
2144 const parts = this.getCodePartsFromUrl(querySource);
2145 const code = parts['code'];
2146 const state = parts['state'];
2147 const sessionState = parts['session_state'];
2148 if (!options.preventClearHashAfterLogin) {
2149 const href = location.origin +
2150 location.pathname +
2151 location.search
2152 .replace(/code=[^&$]*/, '')
2153 .replace(/scope=[^&$]*/, '')
2154 .replace(/state=[^&$]*/, '')
2155 .replace(/session_state=[^&$]*/, '')
2156 .replace(/^\?&/, '?')
2157 .replace(/&$/, '')
2158 .replace(/^\?$/, '')
2159 .replace(/&+/g, '&')
2160 .replace(/\?&/, '?')
2161 .replace(/\?$/, '') +
2162 location.hash;
2163 history.replaceState(null, window.name, href);
2164 }
2165 const [nonceInState, userState] = this.parseState(state);
2166 this.state = userState;
2167 if (parts['error']) {
2168 this.debug('error trying to login');
2169 this.handleLoginError(options, parts);
2170 const err = new OAuthErrorEvent('code_error', {}, parts);
2171 this.eventsSubject.next(err);
2172 return Promise.reject(err);
2173 }
2174 if (!options.disableNonceCheck) {
2175 if (!nonceInState) {
2176 this.saveRequestedRoute();
2177 return Promise.resolve();
2178 }
2179 if (!options.disableOAuth2StateCheck) {
2180 const success = this.validateNonce(nonceInState);
2181 if (!success) {
2182 const event = new OAuthErrorEvent('invalid_nonce_in_state', null);
2183 this.eventsSubject.next(event);
2184 return Promise.reject(event);
2185 }
2186 }
2187 }
2188 this.storeSessionState(sessionState);
2189 if (code) {
2190 await this.getTokenFromCode(code, options);
2191 this.restoreRequestedRoute();
2192 return Promise.resolve();
2193 }
2194 else {
2195 return Promise.resolve();
2196 }
2197 }
2198 saveRequestedRoute() {
2199 if (this.config.preserveRequestedRoute) {
2200 this._storage.setItem('requested_route', window.location.pathname + window.location.search);
2201 }
2202 }
2203 restoreRequestedRoute() {
2204 const requestedRoute = this._storage.getItem('requested_route');
2205 if (requestedRoute) {
2206 history.replaceState(null, '', window.location.origin + requestedRoute);
2207 }
2208 }
2209 /**
2210 * Retrieve the returned auth code from the redirect uri that has been called.
2211 * If required also check hash, as we could use hash location strategy.
2212 */
2213 getCodePartsFromUrl(queryString) {
2214 if (!queryString || queryString.length === 0) {
2215 return this.urlHelper.getHashFragmentParams();
2216 }
2217 // normalize query string
2218 if (queryString.charAt(0) === '?') {
2219 queryString = queryString.substr(1);
2220 }
2221 return this.urlHelper.parseQueryString(queryString);
2222 }
2223 /**
2224 * Get token using an intermediate code. Works for the Authorization Code flow.
2225 */
2226 getTokenFromCode(code, options) {
2227 let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() })
2228 .set('grant_type', 'authorization_code')
2229 .set('code', code)
2230 .set('redirect_uri', options.customRedirectUri || this.redirectUri);
2231 if (!this.disablePKCE) {
2232 let PKCEVerifier;
2233 if (this.saveNoncesInLocalStorage &&
2234 typeof window['localStorage'] !== 'undefined') {
2235 PKCEVerifier = localStorage.getItem('PKCE_verifier');
2236 }
2237 else {
2238 PKCEVerifier = this._storage.getItem('PKCE_verifier');
2239 }
2240 if (!PKCEVerifier) {
2241 console.warn('No PKCE verifier found in oauth storage!');
2242 }
2243 else {
2244 params = params.set('code_verifier', PKCEVerifier);
2245 }
2246 }
2247 return this.fetchAndProcessToken(params, options);
2248 }
2249 fetchAndProcessToken(params, options) {
2250 options = options || {};
2251 this.assertUrlNotNullAndCorrectProtocol(this.tokenEndpoint, 'tokenEndpoint');
2252 let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
2253 if (this.useHttpBasicAuth) {
2254 const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
2255 headers = headers.set('Authorization', 'Basic ' + header);
2256 }
2257 if (!this.useHttpBasicAuth) {
2258 params = params.set('client_id', this.clientId);
2259 }
2260 if (!this.useHttpBasicAuth && this.dummyClientSecret) {
2261 params = params.set('client_secret', this.dummyClientSecret);
2262 }
2263 return new Promise((resolve, reject) => {
2264 if (this.customQueryParams) {
2265 for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
2266 params = params.set(key, this.customQueryParams[key]);
2267 }
2268 }
2269 this.http
2270 .post(this.tokenEndpoint, params, { headers })
2271 .subscribe((tokenResponse) => {
2272 this.debug('refresh tokenResponse', tokenResponse);
2273 this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in ||
2274 this.fallbackAccessTokenExpirationTimeInSec, tokenResponse.scope, this.extractRecognizedCustomParameters(tokenResponse));
2275 if (this.oidc && tokenResponse.id_token) {
2276 this.processIdToken(tokenResponse.id_token, tokenResponse.access_token, options.disableNonceCheck)
2277 .then((result) => {
2278 this.storeIdToken(result);
2279 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
2280 this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
2281 resolve(tokenResponse);
2282 })
2283 .catch((reason) => {
2284 this.eventsSubject.next(new OAuthErrorEvent('token_validation_error', reason));
2285 console.error('Error validating tokens');
2286 console.error(reason);
2287 reject(reason);
2288 });
2289 }
2290 else {
2291 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
2292 this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed'));
2293 resolve(tokenResponse);
2294 }
2295 }, (err) => {
2296 console.error('Error getting token', err);
2297 this.eventsSubject.next(new OAuthErrorEvent('token_refresh_error', err));
2298 reject(err);
2299 });
2300 });
2301 }
2302 /**
2303 * Checks whether there are tokens in the hash fragment
2304 * as a result of the implicit flow. These tokens are
2305 * parsed, validated and used to sign the user in to the
2306 * current client.
2307 *
2308 * @param options Optional options.
2309 */
2310 tryLoginImplicitFlow(options = null) {
2311 options = options || {};
2312 let parts;
2313 if (options.customHashFragment) {
2314 parts = this.urlHelper.getHashFragmentParams(options.customHashFragment);
2315 }
2316 else {
2317 parts = this.urlHelper.getHashFragmentParams();
2318 }
2319 this.debug('parsed url', parts);
2320 const state = parts['state'];
2321 const [nonceInState, userState] = this.parseState(state);
2322 this.state = userState;
2323 if (parts['error']) {
2324 this.debug('error trying to login');
2325 this.handleLoginError(options, parts);
2326 const err = new OAuthErrorEvent('token_error', {}, parts);
2327 this.eventsSubject.next(err);
2328 return Promise.reject(err);
2329 }
2330 const accessToken = parts['access_token'];
2331 const idToken = parts['id_token'];
2332 const sessionState = parts['session_state'];
2333 const grantedScopes = parts['scope'];
2334 if (!this.requestAccessToken && !this.oidc) {
2335 return Promise.reject('Either requestAccessToken or oidc (or both) must be true.');
2336 }
2337 if (this.requestAccessToken && !accessToken) {
2338 return Promise.resolve(false);
2339 }
2340 if (this.requestAccessToken && !options.disableOAuth2StateCheck && !state) {
2341 return Promise.resolve(false);
2342 }
2343 if (this.oidc && !idToken) {
2344 return Promise.resolve(false);
2345 }
2346 if (this.sessionChecksEnabled && !sessionState) {
2347 this.logger.warn('session checks (Session Status Change Notification) ' +
2348 'were activated in the configuration but the id_token ' +
2349 'does not contain a session_state claim');
2350 }
2351 if (this.requestAccessToken && !options.disableNonceCheck) {
2352 const success = this.validateNonce(nonceInState);
2353 if (!success) {
2354 const event = new OAuthErrorEvent('invalid_nonce_in_state', null);
2355 this.eventsSubject.next(event);
2356 return Promise.reject(event);
2357 }
2358 }
2359 if (this.requestAccessToken) {
2360 this.storeAccessTokenResponse(accessToken, null, parts['expires_in'] || this.fallbackAccessTokenExpirationTimeInSec, grantedScopes);
2361 }
2362 if (!this.oidc) {
2363 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
2364 if (this.clearHashAfterLogin && !options.preventClearHashAfterLogin) {
2365 this.clearLocationHash();
2366 }
2367 this.callOnTokenReceivedIfExists(options);
2368 return Promise.resolve(true);
2369 }
2370 return this.processIdToken(idToken, accessToken, options.disableNonceCheck)
2371 .then((result) => {
2372 if (options.validationHandler) {
2373 return options
2374 .validationHandler({
2375 accessToken: accessToken,
2376 idClaims: result.idTokenClaims,
2377 idToken: result.idToken,
2378 state: state,
2379 })
2380 .then(() => result);
2381 }
2382 return result;
2383 })
2384 .then((result) => {
2385 this.storeIdToken(result);
2386 this.storeSessionState(sessionState);
2387 if (this.clearHashAfterLogin && !options.preventClearHashAfterLogin) {
2388 this.clearLocationHash();
2389 }
2390 this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
2391 this.callOnTokenReceivedIfExists(options);
2392 this.inImplicitFlow = false;
2393 return true;
2394 })
2395 .catch((reason) => {
2396 this.eventsSubject.next(new OAuthErrorEvent('token_validation_error', reason));
2397 this.logger.error('Error validating tokens');
2398 this.logger.error(reason);
2399 return Promise.reject(reason);
2400 });
2401 }
2402 parseState(state) {
2403 let nonce = state;
2404 let userState = '';
2405 if (state) {
2406 const idx = state.indexOf(this.config.nonceStateSeparator);
2407 if (idx > -1) {
2408 nonce = state.substr(0, idx);
2409 userState = state.substr(idx + this.config.nonceStateSeparator.length);
2410 }
2411 }
2412 return [nonce, userState];
2413 }
2414 validateNonce(nonceInState) {
2415 let savedNonce;
2416 if (this.saveNoncesInLocalStorage &&
2417 typeof window['localStorage'] !== 'undefined') {
2418 savedNonce = localStorage.getItem('nonce');
2419 }
2420 else {
2421 savedNonce = this._storage.getItem('nonce');
2422 }
2423 if (savedNonce !== nonceInState) {
2424 const err = 'Validating access_token failed, wrong state/nonce.';
2425 console.error(err, savedNonce, nonceInState);
2426 return false;
2427 }
2428 return true;
2429 }
2430 storeIdToken(idToken) {
2431 this._storage.setItem('id_token', idToken.idToken);
2432 this._storage.setItem('id_token_claims_obj', idToken.idTokenClaimsJson);
2433 this._storage.setItem('id_token_expires_at', '' + idToken.idTokenExpiresAt);
2434 this._storage.setItem('id_token_stored_at', '' + this.dateTimeService.now());
2435 }
2436 storeSessionState(sessionState) {
2437 this._storage.setItem('session_state', sessionState);
2438 }
2439 getSessionState() {
2440 return this._storage.getItem('session_state');
2441 }
2442 handleLoginError(options, parts) {
2443 if (options.onLoginError) {
2444 options.onLoginError(parts);
2445 }
2446 if (this.clearHashAfterLogin && !options.preventClearHashAfterLogin) {
2447 this.clearLocationHash();
2448 }
2449 }
2450 getClockSkewInMsec(defaultSkewMsc = 600000) {
2451 if (!this.clockSkewInSec && this.clockSkewInSec !== 0) {
2452 return defaultSkewMsc;
2453 }
2454 return this.clockSkewInSec * 1000;
2455 }
2456 /**
2457 * @ignore
2458 */
2459 processIdToken(idToken, accessToken, skipNonceCheck = false) {
2460 const tokenParts = idToken.split('.');
2461 const headerBase64 = this.padBase64(tokenParts[0]);
2462 const headerJson = b64DecodeUnicode(headerBase64);
2463 const header = JSON.parse(headerJson);
2464 const claimsBase64 = this.padBase64(tokenParts[1]);
2465 const claimsJson = b64DecodeUnicode(claimsBase64);
2466 const claims = JSON.parse(claimsJson);
2467 let savedNonce;
2468 if (this.saveNoncesInLocalStorage &&
2469 typeof window['localStorage'] !== 'undefined') {
2470 savedNonce = localStorage.getItem('nonce');
2471 }
2472 else {
2473 savedNonce = this._storage.getItem('nonce');
2474 }
2475 if (Array.isArray(claims.aud)) {
2476 if (claims.aud.every((v) => v !== this.clientId)) {
2477 const err = 'Wrong audience: ' + claims.aud.join(',');
2478 this.logger.warn(err);
2479 return Promise.reject(err);
2480 }
2481 }
2482 else {
2483 if (claims.aud !== this.clientId) {
2484 const err = 'Wrong audience: ' + claims.aud;
2485 this.logger.warn(err);
2486 return Promise.reject(err);
2487 }
2488 }
2489 if (!claims.sub) {
2490 const err = 'No sub claim in id_token';
2491 this.logger.warn(err);
2492 return Promise.reject(err);
2493 }
2494 /* For now, we only check whether the sub against
2495 * silentRefreshSubject when sessionChecksEnabled is on
2496 * We will reconsider in a later version to do this
2497 * in every other case too.
2498 */
2499 if (this.sessionChecksEnabled &&
2500 this.silentRefreshSubject &&
2501 this.silentRefreshSubject !== claims['sub']) {
2502 const err = 'After refreshing, we got an id_token for another user (sub). ' +
2503 `Expected sub: ${this.silentRefreshSubject}, received sub: ${claims['sub']}`;
2504 this.logger.warn(err);
2505 return Promise.reject(err);
2506 }
2507 if (!claims.iat) {
2508 const err = 'No iat claim in id_token';
2509 this.logger.warn(err);
2510 return Promise.reject(err);
2511 }
2512 if (!this.skipIssuerCheck && claims.iss !== this.issuer) {
2513 const err = 'Wrong issuer: ' + claims.iss;
2514 this.logger.warn(err);
2515 return Promise.reject(err);
2516 }
2517 if (!skipNonceCheck && claims.nonce !== savedNonce) {
2518 const err = 'Wrong nonce: ' + claims.nonce;
2519 this.logger.warn(err);
2520 return Promise.reject(err);
2521 }
2522 // at_hash is not applicable to authorization code flow
2523 // addressing https://github.com/manfredsteyer/angular-oauth2-oidc/issues/661
2524 // i.e. Based on spec the at_hash check is only true for implicit code flow on Ping Federate
2525 // https://www.pingidentity.com/developer/en/resources/openid-connect-developers-guide.html
2526 if (Object.prototype.hasOwnProperty.call(this, 'responseType') &&
2527 (this.responseType === 'code' || this.responseType === 'id_token')) {
2528 this.disableAtHashCheck = true;
2529 }
2530 if (!this.disableAtHashCheck &&
2531 this.requestAccessToken &&
2532 !claims['at_hash']) {
2533 const err = 'An at_hash is needed!';
2534 this.logger.warn(err);
2535 return Promise.reject(err);
2536 }
2537 const now = this.dateTimeService.now();
2538 const issuedAtMSec = claims.iat * 1000;
2539 const expiresAtMSec = claims.exp * 1000;
2540 const clockSkewInMSec = this.getClockSkewInMsec(); // (this.getClockSkewInMsec() || 600) * 1000;
2541 if (issuedAtMSec - clockSkewInMSec >= now ||
2542 expiresAtMSec + clockSkewInMSec - this.decreaseExpirationBySec <= now) {
2543 const err = 'Token has expired';
2544 console.error(err);
2545 console.error({
2546 now: now,
2547 issuedAtMSec: issuedAtMSec,
2548 expiresAtMSec: expiresAtMSec,
2549 });
2550 return Promise.reject(err);
2551 }
2552 const validationParams = {
2553 accessToken: accessToken,
2554 idToken: idToken,
2555 jwks: this.jwks,
2556 idTokenClaims: claims,
2557 idTokenHeader: header,
2558 loadKeys: () => this.loadJwks(),
2559 };
2560 if (this.disableAtHashCheck) {
2561 return this.checkSignature(validationParams).then(() => {
2562 const result = {
2563 idToken: idToken,
2564 idTokenClaims: claims,
2565 idTokenClaimsJson: claimsJson,
2566 idTokenHeader: header,
2567 idTokenHeaderJson: headerJson,
2568 idTokenExpiresAt: expiresAtMSec,
2569 };
2570 return result;
2571 });
2572 }
2573 return this.checkAtHash(validationParams).then((atHashValid) => {
2574 if (!this.disableAtHashCheck && this.requestAccessToken && !atHashValid) {
2575 const err = 'Wrong at_hash';
2576 this.logger.warn(err);
2577 return Promise.reject(err);
2578 }
2579 return this.checkSignature(validationParams).then(() => {
2580 const atHashCheckEnabled = !this.disableAtHashCheck;
2581 const result = {
2582 idToken: idToken,
2583 idTokenClaims: claims,
2584 idTokenClaimsJson: claimsJson,
2585 idTokenHeader: header,
2586 idTokenHeaderJson: headerJson,
2587 idTokenExpiresAt: expiresAtMSec,
2588 };
2589 if (atHashCheckEnabled) {
2590 return this.checkAtHash(validationParams).then((atHashValid) => {
2591 if (this.requestAccessToken && !atHashValid) {
2592 const err = 'Wrong at_hash';
2593 this.logger.warn(err);
2594 return Promise.reject(err);
2595 }
2596 else {
2597 return result;
2598 }
2599 });
2600 }
2601 else {
2602 return result;
2603 }
2604 });
2605 });
2606 }
2607 /**
2608 * Returns the received claims about the user.
2609 */
2610 getIdentityClaims() {
2611 const claims = this._storage.getItem('id_token_claims_obj');
2612 if (!claims) {
2613 return null;
2614 }
2615 return JSON.parse(claims);
2616 }
2617 /**
2618 * Returns the granted scopes from the server.
2619 */
2620 getGrantedScopes() {
2621 const scopes = this._storage.getItem('granted_scopes');
2622 if (!scopes) {
2623 return null;
2624 }
2625 return JSON.parse(scopes);
2626 }
2627 /**
2628 * Returns the current id_token.
2629 */
2630 getIdToken() {
2631 return this._storage ? this._storage.getItem('id_token') : null;
2632 }
2633 padBase64(base64data) {
2634 while (base64data.length % 4 !== 0) {
2635 base64data += '=';
2636 }
2637 return base64data;
2638 }
2639 /**
2640 * Returns the current access_token.
2641 */
2642 getAccessToken() {
2643 return this._storage ? this._storage.getItem('access_token') : null;
2644 }
2645 getRefreshToken() {
2646 return this._storage ? this._storage.getItem('refresh_token') : null;
2647 }
2648 /**
2649 * Returns the expiration date of the access_token
2650 * as milliseconds since 1970.
2651 */
2652 getAccessTokenExpiration() {
2653 if (!this._storage.getItem('expires_at')) {
2654 return null;
2655 }
2656 return parseInt(this._storage.getItem('expires_at'), 10);
2657 }
2658 getAccessTokenStoredAt() {
2659 return parseInt(this._storage.getItem('access_token_stored_at'), 10);
2660 }
2661 getIdTokenStoredAt() {
2662 return parseInt(this._storage.getItem('id_token_stored_at'), 10);
2663 }
2664 /**
2665 * Returns the expiration date of the id_token
2666 * as milliseconds since 1970.
2667 */
2668 getIdTokenExpiration() {
2669 if (!this._storage.getItem('id_token_expires_at')) {
2670 return null;
2671 }
2672 return parseInt(this._storage.getItem('id_token_expires_at'), 10);
2673 }
2674 /**
2675 * Checkes, whether there is a valid access_token.
2676 */
2677 hasValidAccessToken() {
2678 if (this.getAccessToken()) {
2679 const expiresAt = this._storage.getItem('expires_at');
2680 const now = this.dateTimeService.new();
2681 if (expiresAt &&
2682 parseInt(expiresAt, 10) - this.decreaseExpirationBySec <
2683 now.getTime() - this.getClockSkewInMsec()) {
2684 return false;
2685 }
2686 return true;
2687 }
2688 return false;
2689 }
2690 /**
2691 * Checks whether there is a valid id_token.
2692 */
2693 hasValidIdToken() {
2694 if (this.getIdToken()) {
2695 const expiresAt = this._storage.getItem('id_token_expires_at');
2696 const now = this.dateTimeService.new();
2697 if (expiresAt &&
2698 parseInt(expiresAt, 10) - this.decreaseExpirationBySec <
2699 now.getTime() - this.getClockSkewInMsec()) {
2700 return false;
2701 }
2702 return true;
2703 }
2704 return false;
2705 }
2706 /**
2707 * Retrieve a saved custom property of the TokenReponse object. Only if predefined in authconfig.
2708 */
2709 getCustomTokenResponseProperty(requestedProperty) {
2710 return this._storage &&
2711 this.config.customTokenParameters &&
2712 this.config.customTokenParameters.indexOf(requestedProperty) >= 0 &&
2713 this._storage.getItem(requestedProperty) !== null
2714 ? JSON.parse(this._storage.getItem(requestedProperty))
2715 : null;
2716 }
2717 /**
2718 * Returns the auth-header that can be used
2719 * to transmit the access_token to a service
2720 */
2721 authorizationHeader() {
2722 return 'Bearer ' + this.getAccessToken();
2723 }
2724 logOut(customParameters = {}, state = '') {
2725 let noRedirectToLogoutUrl = false;
2726 if (typeof customParameters === 'boolean') {
2727 noRedirectToLogoutUrl = customParameters;
2728 customParameters = {};
2729 }
2730 const id_token = this.getIdToken();
2731 this._storage.removeItem('access_token');
2732 this._storage.removeItem('id_token');
2733 this._storage.removeItem('refresh_token');
2734 if (this.saveNoncesInLocalStorage) {
2735 localStorage.removeItem('nonce');
2736 localStorage.removeItem('PKCE_verifier');
2737 }
2738 else {
2739 this._storage.removeItem('nonce');
2740 this._storage.removeItem('PKCE_verifier');
2741 }
2742 this._storage.removeItem('expires_at');
2743 this._storage.removeItem('id_token_claims_obj');
2744 this._storage.removeItem('id_token_expires_at');
2745 this._storage.removeItem('id_token_stored_at');
2746 this._storage.removeItem('access_token_stored_at');
2747 this._storage.removeItem('granted_scopes');
2748 this._storage.removeItem('session_state');
2749 if (this.config.customTokenParameters) {
2750 this.config.customTokenParameters.forEach((customParam) => this._storage.removeItem(customParam));
2751 }
2752 this.silentRefreshSubject = null;
2753 this.eventsSubject.next(new OAuthInfoEvent('logout'));
2754 if (!this.logoutUrl) {
2755 return;
2756 }
2757 if (noRedirectToLogoutUrl) {
2758 return;
2759 }
2760 // if (!id_token && !this.postLogoutRedirectUri) {
2761 // return;
2762 // }
2763 let logoutUrl;
2764 if (!this.validateUrlForHttps(this.logoutUrl)) {
2765 throw new Error("logoutUrl must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
2766 }
2767 // For backward compatibility
2768 if (this.logoutUrl.indexOf('{{') > -1) {
2769 logoutUrl = this.logoutUrl
2770 .replace(/\{\{id_token\}\}/, encodeURIComponent(id_token))
2771 .replace(/\{\{client_id\}\}/, encodeURIComponent(this.clientId));
2772 }
2773 else {
2774 let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() });
2775 if (id_token) {
2776 params = params.set('id_token_hint', id_token);
2777 }
2778 const postLogoutUrl = this.postLogoutRedirectUri ||
2779 (this.redirectUriAsPostLogoutRedirectUriFallback && this.redirectUri) ||
2780 '';
2781 if (postLogoutUrl) {
2782 params = params.set('post_logout_redirect_uri', postLogoutUrl);
2783 if (state) {
2784 params = params.set('state', state);
2785 }
2786 }
2787 for (const key in customParameters) {
2788 params = params.set(key, customParameters[key]);
2789 }
2790 logoutUrl =
2791 this.logoutUrl +
2792 (this.logoutUrl.indexOf('?') > -1 ? '&' : '?') +
2793 params.toString();
2794 }
2795 this.config.openUri(logoutUrl);
2796 }
2797 /**
2798 * @ignore
2799 */
2800 createAndSaveNonce() {
2801 const that = this; // eslint-disable-line @typescript-eslint/no-this-alias
2802 return this.createNonce().then(function (nonce) {
2803 // Use localStorage for nonce if possible
2804 // localStorage is the only storage who survives a
2805 // redirect in ALL browsers (also IE)
2806 // Otherwiese we'd force teams who have to support
2807 // IE into using localStorage for everything
2808 if (that.saveNoncesInLocalStorage &&
2809 typeof window['localStorage'] !== 'undefined') {
2810 localStorage.setItem('nonce', nonce);
2811 }
2812 else {
2813 that._storage.setItem('nonce', nonce);
2814 }
2815 return nonce;
2816 });
2817 }
2818 /**
2819 * @ignore
2820 */
2821 ngOnDestroy() {
2822 this.clearAccessTokenTimer();
2823 this.clearIdTokenTimer();
2824 this.removeSilentRefreshEventListener();
2825 const silentRefreshFrame = this.document.getElementById(this.silentRefreshIFrameName);
2826 if (silentRefreshFrame) {
2827 silentRefreshFrame.remove();
2828 }
2829 this.stopSessionCheckTimer();
2830 this.removeSessionCheckEventListener();
2831 const sessionCheckFrame = this.document.getElementById(this.sessionCheckIFrameName);
2832 if (sessionCheckFrame) {
2833 sessionCheckFrame.remove();
2834 }
2835 }
2836 createNonce() {
2837 return new Promise((resolve) => {
2838 if (this.rngUrl) {
2839 throw new Error('createNonce with rng-web-api has not been implemented so far');
2840 }
2841 /*
2842 * This alphabet is from:
2843 * https://tools.ietf.org/html/rfc7636#section-4.1
2844 *
2845 * [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
2846 */
2847 const unreserved = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
2848 let size = 45;
2849 let id = '';
2850 const crypto = typeof self === 'undefined' ? null : self.crypto || self['msCrypto'];
2851 if (crypto) {
2852 let bytes = new Uint8Array(size);
2853 crypto.getRandomValues(bytes);
2854 // Needed for IE
2855 if (!bytes.map) {
2856 bytes.map = Array.prototype.map;
2857 }
2858 bytes = bytes.map((x) => unreserved.charCodeAt(x % unreserved.length));
2859 id = String.fromCharCode.apply(null, bytes);
2860 }
2861 else {
2862 while (0 < size--) {
2863 id += unreserved[(Math.random() * unreserved.length) | 0];
2864 }
2865 }
2866 resolve(base64UrlEncode(id));
2867 });
2868 }
2869 async checkAtHash(params) {
2870 if (!this.tokenValidationHandler) {
2871 this.logger.warn('No tokenValidationHandler configured. Cannot check at_hash.');
2872 return true;
2873 }
2874 return this.tokenValidationHandler.validateAtHash(params);
2875 }
2876 checkSignature(params) {
2877 if (!this.tokenValidationHandler) {
2878 this.logger.warn('No tokenValidationHandler configured. Cannot check signature.');
2879 return Promise.resolve(null);
2880 }
2881 return this.tokenValidationHandler.validateSignature(params);
2882 }
2883 /**
2884 * Start the implicit flow or the code flow,
2885 * depending on your configuration.
2886 */
2887 initLoginFlow(additionalState = '', params = {}) {
2888 if (this.responseType === 'code') {
2889 return this.initCodeFlow(additionalState, params);
2890 }
2891 else {
2892 return this.initImplicitFlow(additionalState, params);
2893 }
2894 }
2895 /**
2896 * Starts the authorization code flow and redirects to user to
2897 * the auth servers login url.
2898 */
2899 initCodeFlow(additionalState = '', params = {}) {
2900 if (this.loginUrl !== '') {
2901 this.initCodeFlowInternal(additionalState, params);
2902 }
2903 else {
2904 this.events
2905 .pipe(filter((e) => e.type === 'discovery_document_loaded'))
2906 .subscribe(() => this.initCodeFlowInternal(additionalState, params));
2907 }
2908 }
2909 initCodeFlowInternal(additionalState = '', params = {}) {
2910 if (!this.validateUrlForHttps(this.loginUrl)) {
2911 throw new Error("loginUrl must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS).");
2912 }
2913 let addParams = {};
2914 let loginHint = null;
2915 if (typeof params === 'string') {
2916 loginHint = params;
2917 }
2918 else if (typeof params === 'object') {
2919 addParams = params;
2920 }
2921 this.createLoginUrl(additionalState, loginHint, null, false, addParams)
2922 .then(this.config.openUri)
2923 .catch((error) => {
2924 console.error('Error in initAuthorizationCodeFlow');
2925 console.error(error);
2926 });
2927 }
2928 async createChallangeVerifierPairForPKCE() {
2929 if (!this.crypto) {
2930 throw new Error('PKCE support for code flow needs a CryptoHander. Did you import the OAuthModule using forRoot() ?');
2931 }
2932 const verifier = await this.createNonce();
2933 const challengeRaw = await this.crypto.calcHash(verifier, 'sha-256');
2934 const challenge = base64UrlEncode(challengeRaw);
2935 return [challenge, verifier];
2936 }
2937 extractRecognizedCustomParameters(tokenResponse) {
2938 const foundParameters = new Map();
2939 if (!this.config.customTokenParameters) {
2940 return foundParameters;
2941 }
2942 this.config.customTokenParameters.forEach((recognizedParameter) => {
2943 if (tokenResponse[recognizedParameter]) {
2944 foundParameters.set(recognizedParameter, JSON.stringify(tokenResponse[recognizedParameter]));
2945 }
2946 });
2947 return foundParameters;
2948 }
2949 /**
2950 * Revokes the auth token to secure the vulnarability
2951 * of the token issued allowing the authorization server to clean
2952 * up any security credentials associated with the authorization
2953 */
2954 revokeTokenAndLogout(customParameters = {}, ignoreCorsIssues = false) {
2955 const revokeEndpoint = this.revocationEndpoint;
2956 const accessToken = this.getAccessToken();
2957 const refreshToken = this.getRefreshToken();
2958 if (!accessToken) {
2959 return Promise.resolve();
2960 }
2961 let params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() });
2962 let headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
2963 if (this.useHttpBasicAuth) {
2964 const header = btoa(`${this.clientId}:${this.dummyClientSecret}`);
2965 headers = headers.set('Authorization', 'Basic ' + header);
2966 }
2967 if (!this.useHttpBasicAuth) {
2968 params = params.set('client_id', this.clientId);
2969 }
2970 if (!this.useHttpBasicAuth && this.dummyClientSecret) {
2971 params = params.set('client_secret', this.dummyClientSecret);
2972 }
2973 if (this.customQueryParams) {
2974 for (const key of Object.getOwnPropertyNames(this.customQueryParams)) {
2975 params = params.set(key, this.customQueryParams[key]);
2976 }
2977 }
2978 return new Promise((resolve, reject) => {
2979 let revokeAccessToken;
2980 let revokeRefreshToken;
2981 if (accessToken) {
2982 const revokationParams = params
2983 .set('token', accessToken)
2984 .set('token_type_hint', 'access_token');
2985 revokeAccessToken = this.http.post(revokeEndpoint, revokationParams, { headers });
2986 }
2987 else {
2988 revokeAccessToken = of(null);
2989 }
2990 if (refreshToken) {
2991 const revokationParams = params
2992 .set('token', refreshToken)
2993 .set('token_type_hint', 'refresh_token');
2994 revokeRefreshToken = this.http.post(revokeEndpoint, revokationParams, { headers });
2995 }
2996 else {
2997 revokeRefreshToken = of(null);
2998 }
2999 if (ignoreCorsIssues) {
3000 revokeAccessToken = revokeAccessToken.pipe(catchError((err) => {
3001 if (err.status === 0) {
3002 return of(null);
3003 }
3004 return throwError(err);
3005 }));
3006 revokeRefreshToken = revokeRefreshToken.pipe(catchError((err) => {
3007 if (err.status === 0) {
3008 return of(null);
3009 }
3010 return throwError(err);
3011 }));
3012 }
3013 combineLatest([revokeAccessToken, revokeRefreshToken]).subscribe((res) => {
3014 this.logOut(customParameters);
3015 resolve(res);
3016 this.logger.info('Token successfully revoked');
3017 }, (err) => {
3018 this.logger.error('Error revoking token', err);
3019 this.eventsSubject.next(new OAuthErrorEvent('token_revoke_error', err));
3020 reject(err);
3021 });
3022 });
3023 }
3024 /**
3025 * Clear location.hash if it's present
3026 */
3027 clearLocationHash() {
3028 // Checking for empty hash is necessary for Firefox
3029 // as setting an empty hash to an empty string adds # to the URL
3030 if (location.hash != '') {
3031 location.hash = '';
3032 }
3033 }
3034 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthService, deps: [{ token: i0.NgZone }, { token: i1.HttpClient }, { token: OAuthStorage, optional: true }, { token: ValidationHandler, optional: true }, { token: AuthConfig, optional: true }, { token: UrlHelperService }, { token: OAuthLogger }, { token: HashHandler, optional: true }, { token: DOCUMENT }, { token: DateTimeProvider }], target: i0.ɵɵFactoryTarget.Injectable }); }
3035 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthService }); }
3036}
3037i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthService, decorators: [{
3038 type: Injectable
3039 }], ctorParameters: () => [{ type: i0.NgZone }, { type: i1.HttpClient }, { type: OAuthStorage, decorators: [{
3040 type: Optional
3041 }] }, { type: ValidationHandler, decorators: [{
3042 type: Optional
3043 }] }, { type: AuthConfig, decorators: [{
3044 type: Optional
3045 }] }, { type: UrlHelperService }, { type: OAuthLogger }, { type: HashHandler, decorators: [{
3046 type: Optional
3047 }] }, { type: Document, decorators: [{
3048 type: Inject,
3049 args: [DOCUMENT]
3050 }] }, { type: DateTimeProvider }] });
3051
3052class OAuthResourceServerErrorHandler {
3053}
3054class OAuthNoopResourceServerErrorHandler {
3055 handleError(err) {
3056 return throwError(err);
3057 }
3058}
3059
3060class DefaultOAuthInterceptor {
3061 constructor(oAuthService, errorHandler, moduleConfig) {
3062 this.oAuthService = oAuthService;
3063 this.errorHandler = errorHandler;
3064 this.moduleConfig = moduleConfig;
3065 }
3066 checkUrl(url) {
3067 if (this.moduleConfig.resourceServer.customUrlValidation) {
3068 return this.moduleConfig.resourceServer.customUrlValidation(url);
3069 }
3070 if (this.moduleConfig.resourceServer.allowedUrls) {
3071 return !!this.moduleConfig.resourceServer.allowedUrls.find((u) => url.toLowerCase().startsWith(u.toLowerCase()));
3072 }
3073 return true;
3074 }
3075 intercept(req, next) {
3076 const url = req.url.toLowerCase();
3077 if (!this.moduleConfig ||
3078 !this.moduleConfig.resourceServer ||
3079 !this.checkUrl(url)) {
3080 return next.handle(req);
3081 }
3082 const sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken;
3083 if (!sendAccessToken) {
3084 return next
3085 .handle(req)
3086 .pipe(catchError((err) => this.errorHandler.handleError(err)));
3087 }
3088 return merge(of(this.oAuthService.getAccessToken()).pipe(filter((token) => !!token)), this.oAuthService.events.pipe(filter((e) => e.type === 'token_received'), timeout(this.oAuthService.waitForTokenInMsec || 0), catchError(() => of(null)), // timeout is not an error
3089 map(() => this.oAuthService.getAccessToken()))).pipe(take(1), mergeMap((token) => {
3090 if (token) {
3091 const header = 'Bearer ' + token;
3092 const headers = req.headers.set('Authorization', header);
3093 req = req.clone({ headers });
3094 }
3095 return next
3096 .handle(req)
3097 .pipe(catchError((err) => this.errorHandler.handleError(err)));
3098 }));
3099 }
3100 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultOAuthInterceptor, deps: [{ token: OAuthService }, { token: OAuthResourceServerErrorHandler }, { token: OAuthModuleConfig, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
3101 static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultOAuthInterceptor }); }
3102}
3103i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: DefaultOAuthInterceptor, decorators: [{
3104 type: Injectable
3105 }], ctorParameters: () => [{ type: OAuthService }, { type: OAuthResourceServerErrorHandler }, { type: OAuthModuleConfig, decorators: [{
3106 type: Optional
3107 }] }] });
3108
3109function createDefaultLogger() {
3110 return console;
3111}
3112function createDefaultStorage() {
3113 return typeof sessionStorage !== 'undefined'
3114 ? sessionStorage
3115 : new MemoryStorage();
3116}
3117
3118function provideOAuthClient(config = null, validationHandlerClass = NullValidationHandler) {
3119 return makeEnvironmentProviders([
3120 OAuthService,
3121 UrlHelperService,
3122 { provide: OAuthLogger, useFactory: createDefaultLogger },
3123 { provide: OAuthStorage, useFactory: createDefaultStorage },
3124 { provide: ValidationHandler, useClass: validationHandlerClass },
3125 { provide: HashHandler, useClass: DefaultHashHandler },
3126 {
3127 provide: OAuthResourceServerErrorHandler,
3128 useClass: OAuthNoopResourceServerErrorHandler,
3129 },
3130 { provide: OAuthModuleConfig, useValue: config },
3131 {
3132 provide: HTTP_INTERCEPTORS,
3133 useClass: DefaultOAuthInterceptor,
3134 multi: true,
3135 },
3136 { provide: DateTimeProvider, useClass: SystemDateTimeProvider },
3137 ]);
3138}
3139
3140class OAuthModule {
3141 static forRoot(config = null, validationHandlerClass = NullValidationHandler) {
3142 return {
3143 ngModule: OAuthModule,
3144 providers: [provideOAuthClient(config, validationHandlerClass)],
3145 };
3146 }
3147 static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
3148 static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.0.7", ngImport: i0, type: OAuthModule, imports: [CommonModule] }); }
3149 static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthModule, imports: [CommonModule] }); }
3150}
3151i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.7", ngImport: i0, type: OAuthModule, decorators: [{
3152 type: NgModule,
3153 args: [{
3154 imports: [CommonModule],
3155 declarations: [],
3156 exports: [],
3157 }]
3158 }] });
3159
3160const err = `PLEASE READ THIS CAREFULLY:
3161
3162Beginning with angular-oauth2-oidc version 9, the JwksValidationHandler
3163has been moved to an library of its own. If you need it for implementing
3164OAuth2/OIDC **implicit flow**, please install it using npm:
3165
3166 npm i angular-oauth2-oidc-jwks --save
3167
3168After that, you can import it into your application:
3169
3170 import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
3171
3172Please note, that this dependency is not needed for the **code flow**,
3173which is nowadays the **recommented** one for single page applications.
3174This also results in smaller bundle sizes.
3175`;
3176/**
3177 * This is just a dummy of the JwksValidationHandler
3178 * telling the users that the real one has been moved
3179 * to an library of its own, namely angular-oauth2-oidc-utils
3180 */
3181class JwksValidationHandler extends NullValidationHandler {
3182 constructor() {
3183 super();
3184 console.error(err);
3185 }
3186}
3187
3188const AUTH_CONFIG = new InjectionToken('AUTH_CONFIG');
3189
3190/**
3191 * Generated bundle index. Do not edit.
3192 */
3193
3194export { AUTH_CONFIG, AbstractValidationHandler, AuthConfig, DateTimeProvider, DefaultHashHandler, DefaultOAuthInterceptor, HashHandler, JwksValidationHandler, LoginOptions, MemoryStorage, NullValidationHandler, OAuthErrorEvent, OAuthEvent, OAuthInfoEvent, OAuthLogger, OAuthModule, OAuthModuleConfig, OAuthNoopResourceServerErrorHandler, OAuthResourceServerConfig, OAuthResourceServerErrorHandler, OAuthService, OAuthStorage, OAuthSuccessEvent, ReceivedTokens, SystemDateTimeProvider, UrlHelperService, ValidationHandler, provideOAuthClient };
3195//# sourceMappingURL=angular-oauth2-oidc.mjs.map