@auth0/auth0-spa-js 1.22.3 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +50 -41
  2. package/dist/auth0-spa-js.development.js +996 -4901
  3. package/dist/auth0-spa-js.development.js.map +1 -1
  4. package/dist/auth0-spa-js.production.esm.js +1 -1
  5. package/dist/auth0-spa-js.production.esm.js.map +1 -1
  6. package/dist/auth0-spa-js.production.js +1 -1
  7. package/dist/auth0-spa-js.production.js.map +1 -1
  8. package/dist/lib/auth0-spa-js.cjs.js +1768 -5532
  9. package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
  10. package/dist/typings/Auth0Client.d.ts +21 -41
  11. package/dist/typings/Auth0Client.utils.d.ts +26 -0
  12. package/dist/typings/cache/cache-manager.d.ts +11 -7
  13. package/dist/typings/cache/shared.d.ts +16 -11
  14. package/dist/typings/constants.d.ts +0 -7
  15. package/dist/typings/errors.d.ts +0 -4
  16. package/dist/typings/global.d.ts +125 -167
  17. package/dist/typings/index.d.ts +3 -13
  18. package/dist/typings/transaction-manager.d.ts +1 -1
  19. package/dist/typings/utils.d.ts +8 -7
  20. package/dist/typings/version.d.ts +1 -1
  21. package/package.json +35 -36
  22. package/src/Auth0Client.ts +407 -562
  23. package/src/Auth0Client.utils.ts +73 -0
  24. package/src/cache/cache-localstorage.ts +1 -1
  25. package/src/cache/cache-manager.ts +58 -29
  26. package/src/cache/shared.ts +29 -17
  27. package/src/constants.ts +0 -17
  28. package/src/errors.ts +10 -14
  29. package/src/global.ts +132 -183
  30. package/src/http.ts +0 -5
  31. package/src/index.ts +15 -14
  32. package/src/jwt.ts +3 -3
  33. package/src/storage.ts +1 -1
  34. package/src/transaction-manager.ts +1 -1
  35. package/src/utils.ts +37 -42
  36. package/src/version.ts +1 -1
  37. package/src/worker/token.worker.ts +4 -3
  38. package/dist/typings/index.cjs.d.ts +0 -5
  39. package/dist/typings/user-agent.d.ts +0 -1
  40. package/src/index.cjs.ts +0 -23
  41. package/src/user-agent.ts +0 -1
@@ -10,7 +10,10 @@ import {
10
10
  sha256,
11
11
  bufferToBase64UrlEncoded,
12
12
  validateCrypto,
13
- openPopup
13
+ openPopup,
14
+ getDomain,
15
+ getTokenIssuer,
16
+ parseNumber
14
17
  } from './utils';
15
18
 
16
19
  import { oauthToken } from './api';
@@ -20,12 +23,14 @@ import { getUniqueScopes } from './scope';
20
23
  import {
21
24
  InMemoryCache,
22
25
  ICache,
23
- LocalStorageCache,
24
26
  CacheKey,
25
- CacheManager
27
+ CacheManager,
28
+ CacheEntry,
29
+ IdTokenEntry,
30
+ CACHE_KEY_ID_TOKEN_SUFFIX
26
31
  } from './cache';
27
32
 
28
- import TransactionManager from './transaction-manager';
33
+ import { TransactionManager } from './transaction-manager';
29
34
  import { verify as verifyIdToken } from './jwt';
30
35
  import {
31
36
  AuthenticationError,
@@ -47,7 +52,6 @@ import {
47
52
  DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
48
53
  MISSING_REFRESH_TOKEN_ERROR_MESSAGE,
49
54
  DEFAULT_SCOPE,
50
- RECOVERABLE_ERRORS,
51
55
  DEFAULT_SESSION_CHECK_EXPIRY_DAYS,
52
56
  DEFAULT_AUTH0_CLIENT,
53
57
  INVALID_REFRESH_TOKEN_ERROR_MESSAGE,
@@ -57,19 +61,15 @@ import {
57
61
 
58
62
  import {
59
63
  Auth0ClientOptions,
60
- BaseLoginOptions,
64
+ AuthorizationParams,
61
65
  AuthorizeOptions,
62
66
  RedirectLoginOptions,
63
67
  PopupLoginOptions,
64
68
  PopupConfigOptions,
65
- GetUserOptions,
66
- GetIdTokenClaimsOptions,
67
69
  RedirectLoginResult,
68
70
  GetTokenSilentlyOptions,
69
71
  GetTokenWithPopupOptions,
70
72
  LogoutOptions,
71
- RefreshTokenOptions,
72
- OAuthTokenOptions,
73
73
  CacheLocation,
74
74
  LogoutUrlOptions,
75
75
  User,
@@ -80,9 +80,16 @@ import {
80
80
 
81
81
  // @ts-ignore
82
82
  import TokenWorker from './worker/token.worker.ts';
83
- import { isIE11 } from './user-agent';
84
83
  import { singlePromise, retryPromise } from './promise-utils';
85
84
  import { CacheKeyManifest } from './cache/key-manifest';
85
+ import {
86
+ buildIsAuthenticatedCookieName,
87
+ buildOrganizationHintCookieName,
88
+ cacheFactory,
89
+ getAuthorizeParams,
90
+ GET_TOKEN_SILENTLY_LOCK_KEY,
91
+ OLD_IS_AUTHENTICATED_COOKIE_NAME
92
+ } from './Auth0Client.utils';
86
93
 
87
94
  /**
88
95
  * @ignore
@@ -99,110 +106,14 @@ type GetTokenSilentlyResult = TokenEndpointResponse & {
99
106
  */
100
107
  const lock = new Lock();
101
108
 
102
- /**
103
- * @ignore
104
- */
105
- const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently';
106
-
107
- /**
108
- * @ignore
109
- */
110
- const buildOrganizationHintCookieName = (clientId: string) =>
111
- `auth0.${clientId}.organization_hint`;
112
-
113
- /**
114
- * @ignore
115
- */
116
- const OLD_IS_AUTHENTICATED_COOKIE_NAME = 'auth0.is.authenticated';
117
-
118
- /**
119
- * @ignore
120
- */
121
- const buildIsAuthenticatedCookieName = (clientId: string) =>
122
- `auth0.${clientId}.is.authenticated`;
123
-
124
- /**
125
- * @ignore
126
- */
127
- const cacheLocationBuilders: Record<string, () => ICache> = {
128
- memory: () => new InMemoryCache().enclosedCache,
129
- localstorage: () => new LocalStorageCache()
130
- };
131
-
132
- /**
133
- * @ignore
134
- */
135
- const cacheFactory = (location: string) => {
136
- return cacheLocationBuilders[location];
137
- };
138
-
139
- /**
140
- * @ignore
141
- */
142
- const supportWebWorker = () => !isIE11();
143
-
144
- /**
145
- * @ignore
146
- */
147
- const getTokenIssuer = (issuer: string, domainUrl: string) => {
148
- if (issuer) {
149
- return issuer.startsWith('https://') ? issuer : `https://${issuer}/`;
150
- }
151
-
152
- return `${domainUrl}/`;
153
- };
154
-
155
- /**
156
- * @ignore
157
- */
158
- const getDomain = (domainUrl: string) => {
159
- if (!/^https?:\/\//.test(domainUrl)) {
160
- return `https://${domainUrl}`;
161
- }
162
-
163
- return domainUrl;
164
- };
165
-
166
- /**
167
- * @ignore
168
- */
169
- const getCustomInitialOptions = (
170
- options: Auth0ClientOptions
171
- ): BaseLoginOptions => {
172
- const {
173
- advancedOptions,
174
- audience,
175
- auth0Client,
176
- authorizeTimeoutInSeconds,
177
- cacheLocation,
178
- cache,
179
- client_id,
180
- domain,
181
- issuer,
182
- leeway,
183
- max_age,
184
- nowProvider,
185
- redirect_uri,
186
- scope,
187
- useRefreshTokens,
188
- useRefreshTokensFallback,
189
- useCookiesForTransactions,
190
- useFormData,
191
- ...customParams
192
- } = options;
193
- return customParams;
194
- };
195
-
196
109
  /**
197
110
  * Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce).
198
111
  */
199
- export default class Auth0Client {
112
+ export class Auth0Client {
200
113
  private readonly transactionManager: TransactionManager;
201
114
  private readonly cacheManager: CacheManager;
202
- private readonly customOptions: BaseLoginOptions;
203
115
  private readonly domainUrl: string;
204
116
  private readonly tokenIssuer: string;
205
- private readonly defaultScope: string;
206
117
  private readonly scope: string;
207
118
  private readonly cookieStorage: ClientStorage;
208
119
  private readonly sessionCheckExpiryDays: number;
@@ -210,12 +121,31 @@ export default class Auth0Client {
210
121
  private readonly isAuthenticatedCookieName: string;
211
122
  private readonly nowProvider: () => number | Promise<number>;
212
123
  private readonly httpTimeoutMs: number;
213
- private readonly useRefreshTokensFallback: boolean;
124
+ private readonly options: Auth0ClientOptions & { authorizationParams: AuthorizationParams };
125
+ private readonly userCache: ICache = new InMemoryCache().enclosedCache;
214
126
 
215
127
  cacheLocation: CacheLocation;
128
+
216
129
  private worker: Worker;
217
130
 
218
- constructor(private options: Auth0ClientOptions) {
131
+ private readonly defaultOptions: Partial<Auth0ClientOptions> = {
132
+ authorizationParams: {
133
+ scope: DEFAULT_SCOPE
134
+ },
135
+ useRefreshTokensFallback: false,
136
+ useFormData: true
137
+ };
138
+
139
+ constructor(options: Auth0ClientOptions) {
140
+ this.options = {
141
+ ...this.defaultOptions,
142
+ ...options,
143
+ authorizationParams: {
144
+ ...this.defaultOptions.authorizationParams,
145
+ ...options.authorizationParams
146
+ }
147
+ };
148
+
219
149
  typeof window !== 'undefined' && validateCrypto();
220
150
 
221
151
  if (options.cache && options.cacheLocation) {
@@ -248,11 +178,11 @@ export default class Auth0Client {
248
178
  : CookieStorageWithLegacySameSite;
249
179
 
250
180
  this.orgHintCookieName = buildOrganizationHintCookieName(
251
- this.options.client_id
181
+ this.options.clientId
252
182
  );
253
183
 
254
184
  this.isAuthenticatedCookieName = buildIsAuthenticatedCookieName(
255
- this.options.client_id
185
+ this.options.clientId
256
186
  );
257
187
 
258
188
  this.sessionCheckExpiryDays =
@@ -262,11 +192,19 @@ export default class Auth0Client {
262
192
  ? this.cookieStorage
263
193
  : SessionStorage;
264
194
 
265
- this.scope = this.options.scope;
195
+ // Construct the scopes based on the following:
196
+ // 1. Always include `openid`
197
+ // 2. Include the scopes provided in `authorizationParams. This defaults to `profile email`
198
+ // 3. Add `offline_access` if `useRefreshTokens` is enabled
199
+ this.scope = getUniqueScopes(
200
+ 'openid',
201
+ this.options.authorizationParams.scope,
202
+ this.options.useRefreshTokens ? 'offline_access' : ''
203
+ );
266
204
 
267
205
  this.transactionManager = new TransactionManager(
268
206
  transactionStorage,
269
- this.options.client_id
207
+ this.options.clientId
270
208
  );
271
209
 
272
210
  this.nowProvider = this.options.nowProvider || DEFAULT_NOW_PROVIDER;
@@ -274,7 +212,7 @@ export default class Auth0Client {
274
212
  this.cacheManager = new CacheManager(
275
213
  cache,
276
214
  !cache.allKeys
277
- ? new CacheKeyManifest(cache, this.options.client_id)
215
+ ? new CacheKeyManifest(cache, this.options.clientId)
278
216
  : null,
279
217
  this.nowProvider
280
218
  );
@@ -282,35 +220,15 @@ export default class Auth0Client {
282
220
  this.domainUrl = getDomain(this.options.domain);
283
221
  this.tokenIssuer = getTokenIssuer(this.options.issuer, this.domainUrl);
284
222
 
285
- this.defaultScope = getUniqueScopes(
286
- 'openid',
287
- this.options?.advancedOptions?.defaultScope !== undefined
288
- ? this.options.advancedOptions.defaultScope
289
- : DEFAULT_SCOPE
290
- );
291
-
292
- // If using refresh tokens, automatically specify the `offline_access` scope.
293
- // Note we cannot add this to 'defaultScope' above as the scopes are used in the
294
- // cache keys - changing the order could invalidate the keys
295
- if (this.options.useRefreshTokens) {
296
- this.scope = getUniqueScopes(this.scope, 'offline_access');
297
- }
298
-
299
- // Don't use web workers unless using refresh tokens in memory and not IE11
223
+ // Don't use web workers unless using refresh tokens in memory
300
224
  if (
301
225
  typeof window !== 'undefined' &&
302
226
  window.Worker &&
303
227
  this.options.useRefreshTokens &&
304
- this.cacheLocation === CACHE_LOCATION_MEMORY &&
305
- supportWebWorker()
228
+ this.cacheLocation === CACHE_LOCATION_MEMORY
306
229
  ) {
307
230
  this.worker = new TokenWorker();
308
231
  }
309
-
310
- this.customOptions = getCustomInitialOptions(options);
311
-
312
- this.useRefreshTokensFallback =
313
- this.options.useRefreshTokensFallback !== false;
314
232
  }
315
233
 
316
234
  private _url(path: string) {
@@ -320,52 +238,6 @@ export default class Auth0Client {
320
238
  return `${this.domainUrl}${path}&auth0Client=${auth0Client}`;
321
239
  }
322
240
 
323
- private _getParams(
324
- authorizeOptions: BaseLoginOptions,
325
- state: string,
326
- nonce: string,
327
- code_challenge: string,
328
- redirect_uri: string
329
- ): AuthorizeOptions {
330
- // These options should be excluded from the authorize URL,
331
- // as they're options for the client and not for the IdP.
332
- // ** IMPORTANT ** If adding a new client option, include it in this destructure list.
333
- const {
334
- useRefreshTokens,
335
- useCookiesForTransactions,
336
- useFormData,
337
- auth0Client,
338
- cacheLocation,
339
- advancedOptions,
340
- detailedResponse,
341
- nowProvider,
342
- authorizeTimeoutInSeconds,
343
- legacySameSiteCookie,
344
- sessionCheckExpiryDays,
345
- domain,
346
- leeway,
347
- httpTimeoutInSeconds,
348
- ...loginOptions
349
- } = this.options;
350
-
351
- return {
352
- ...loginOptions,
353
- ...authorizeOptions,
354
- scope: getUniqueScopes(
355
- this.defaultScope,
356
- this.scope,
357
- authorizeOptions.scope
358
- ),
359
- response_type: 'code',
360
- response_mode: 'query',
361
- state,
362
- nonce,
363
- redirect_uri: redirect_uri || this.options.redirect_uri,
364
- code_challenge,
365
- code_challenge_method: 'S256'
366
- };
367
- }
368
-
369
241
  private _authorizeUrl(authorizeOptions: AuthorizeOptions) {
370
242
  return this._url(`/authorize?${createQueryParams(authorizeOptions)}`);
371
243
  }
@@ -379,23 +251,16 @@ export default class Auth0Client {
379
251
 
380
252
  return verifyIdToken({
381
253
  iss: this.tokenIssuer,
382
- aud: this.options.client_id,
254
+ aud: this.options.clientId,
383
255
  id_token,
384
256
  nonce,
385
257
  organizationId,
386
258
  leeway: this.options.leeway,
387
- max_age: this._parseNumber(this.options.max_age),
259
+ max_age: parseNumber(this.options.authorizationParams.max_age),
388
260
  now
389
261
  });
390
262
  }
391
263
 
392
- private _parseNumber(value: any): number {
393
- if (typeof value !== 'string') {
394
- return value;
395
- }
396
- return parseInt(value, 10) || undefined;
397
- }
398
-
399
264
  private _processOrgIdHint(organizationId?: string) {
400
265
  if (organizationId) {
401
266
  this.cookieStorage.save(this.orgHintCookieName, organizationId, {
@@ -403,56 +268,55 @@ export default class Auth0Client {
403
268
  cookieDomain: this.options.cookieDomain
404
269
  });
405
270
  } else {
406
- this.cookieStorage.remove(this.orgHintCookieName, { cookieDomain: this.options.cookieDomain });
271
+ this.cookieStorage.remove(this.orgHintCookieName, {
272
+ cookieDomain: this.options.cookieDomain
273
+ });
407
274
  }
408
275
  }
409
276
 
410
- /**
411
- * ```js
412
- * await auth0.buildAuthorizeUrl(options);
413
- * ```
414
- *
415
- * Builds an `/authorize` URL for loginWithRedirect using the parameters
416
- * provided as arguments. Random and secure `state` and `nonce`
417
- * parameters will be auto-generated.
418
- *
419
- * @param options
420
- */
421
- public async buildAuthorizeUrl(
422
- options: RedirectLoginOptions = {}
423
- ): Promise<string> {
424
- const { redirect_uri, appState, ...authorizeOptions } = options;
425
-
426
- const stateIn = encode(createRandomString());
427
- const nonceIn = encode(createRandomString());
277
+ private async _prepareAuthorizeUrl(
278
+ authorizationParams: AuthorizationParams,
279
+ authorizeOptions?: Partial<AuthorizeOptions>,
280
+ fallbackRedirectUri?: string
281
+ ): Promise<{
282
+ scope: string;
283
+ audience: string;
284
+ redirect_uri: string;
285
+ nonce: string;
286
+ code_verifier: string;
287
+ state: string;
288
+ url: string;
289
+ }> {
290
+ const state = encode(createRandomString());
291
+ const nonce = encode(createRandomString());
428
292
  const code_verifier = createRandomString();
429
293
  const code_challengeBuffer = await sha256(code_verifier);
430
294
  const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
431
- const fragment = options.fragment ? `#${options.fragment}` : '';
432
295
 
433
- const params = this._getParams(
434
- authorizeOptions,
435
- stateIn,
436
- nonceIn,
296
+ const params = getAuthorizeParams(
297
+ this.options,
298
+ this.scope,
299
+ authorizationParams,
300
+ state,
301
+ nonce,
437
302
  code_challenge,
438
- redirect_uri
303
+ authorizationParams?.redirect_uri ||
304
+ this.options.authorizationParams.redirect_uri ||
305
+ fallbackRedirectUri,
306
+ authorizeOptions?.response_mode
439
307
  );
440
308
 
441
309
  const url = this._authorizeUrl(params);
442
- const organizationId = options.organization || this.options.organization;
443
310
 
444
- this.transactionManager.create({
445
- nonce: nonceIn,
311
+ return {
312
+ nonce,
446
313
  code_verifier,
447
- appState,
448
314
  scope: params.scope,
449
315
  audience: params.audience || 'default',
450
316
  redirect_uri: params.redirect_uri,
451
- state: stateIn,
452
- ...(organizationId && { organizationId })
453
- });
454
-
455
- return url + fragment;
317
+ state,
318
+ url
319
+ };
456
320
  }
457
321
 
458
322
  /**
@@ -495,27 +359,13 @@ export default class Auth0Client {
495
359
  }
496
360
  }
497
361
 
498
- const { ...authorizeOptions } = options;
499
- const stateIn = encode(createRandomString());
500
- const nonceIn = encode(createRandomString());
501
- const code_verifier = createRandomString();
502
- const code_challengeBuffer = await sha256(code_verifier);
503
- const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
504
-
505
- const params = this._getParams(
506
- authorizeOptions,
507
- stateIn,
508
- nonceIn,
509
- code_challenge,
510
- this.options.redirect_uri || window.location.origin
362
+ const params = await this._prepareAuthorizeUrl(
363
+ options.authorizationParams,
364
+ { response_mode: 'web_message' },
365
+ window.location.origin
511
366
  );
512
367
 
513
- const url = this._authorizeUrl({
514
- ...params,
515
- response_mode: 'web_message'
516
- });
517
-
518
- config.popup.location.href = url;
368
+ config.popup.location.href = params.url;
519
369
 
520
370
  const codeResult = await runPopup({
521
371
  ...config,
@@ -525,51 +375,28 @@ export default class Auth0Client {
525
375
  DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
526
376
  });
527
377
 
528
- if (stateIn !== codeResult.state) {
378
+ if (params.state !== codeResult.state) {
529
379
  throw new Error('Invalid state');
530
380
  }
531
381
 
532
- const authResult = await oauthToken(
382
+ const organizationId =
383
+ options.authorizationParams?.organization ||
384
+ this.options.authorizationParams.organization;
385
+
386
+ await this._requestToken(
533
387
  {
534
388
  audience: params.audience,
535
389
  scope: params.scope,
536
- baseUrl: this.domainUrl,
537
- client_id: this.options.client_id,
538
- code_verifier,
539
- code: codeResult.code,
390
+ code_verifier: params.code_verifier,
540
391
  grant_type: 'authorization_code',
541
- redirect_uri: params.redirect_uri,
542
- auth0Client: this.options.auth0Client,
543
- useFormData: this.options.useFormData,
544
- timeout: this.httpTimeoutMs
545
- } as OAuthTokenOptions,
546
- this.worker
547
- );
548
-
549
- const organizationId = options.organization || this.options.organization;
550
-
551
- const decodedToken = await this._verifyIdToken(
552
- authResult.id_token,
553
- nonceIn,
554
- organizationId
392
+ code: codeResult.code,
393
+ redirect_uri: params.redirect_uri
394
+ },
395
+ {
396
+ nonceIn: params.nonce,
397
+ organizationId
398
+ }
555
399
  );
556
-
557
- const cacheEntry = {
558
- ...authResult,
559
- decodedToken,
560
- scope: params.scope,
561
- audience: params.audience || 'default',
562
- client_id: this.options.client_id
563
- };
564
-
565
- await this.cacheManager.set(cacheEntry);
566
-
567
- this.cookieStorage.save(this.isAuthenticatedCookieName, true, {
568
- daysUntilExpire: this.sessionCheckExpiryDays,
569
- cookieDomain: this.options.cookieDomain
570
- });
571
-
572
- this._processOrgIdHint(decodedToken.claims.org_id);
573
400
  }
574
401
 
575
402
  /**
@@ -580,28 +407,12 @@ export default class Auth0Client {
580
407
  * Returns the user information if available (decoded
581
408
  * from the `id_token`).
582
409
  *
583
- * If you provide an audience or scope, they should match an existing Access Token
584
- * (the SDK stores a corresponding ID Token with every Access Token, and uses the
585
- * scope and audience to look up the ID Token)
586
- *
587
410
  * @typeparam TUser The type to return, has to extend {@link User}.
588
- * @param options
589
411
  */
590
- public async getUser<TUser extends User>(
591
- options: GetUserOptions = {}
592
- ): Promise<TUser | undefined> {
593
- const audience = options.audience || this.options.audience || 'default';
594
- const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
595
-
596
- const cache = await this.cacheManager.get(
597
- new CacheKey({
598
- client_id: this.options.client_id,
599
- audience,
600
- scope
601
- })
602
- );
412
+ public async getUser<TUser extends User>(): Promise<TUser | undefined> {
413
+ const cache = await this._getIdTokenFromCache();
603
414
 
604
- return cache && cache.decodedToken && (cache.decodedToken.user as TUser);
415
+ return cache?.decodedToken?.user as TUser;
605
416
  }
606
417
 
607
418
  /**
@@ -610,28 +421,11 @@ export default class Auth0Client {
610
421
  * ```
611
422
  *
612
423
  * Returns all claims from the id_token if available.
613
- *
614
- * If you provide an audience or scope, they should match an existing Access Token
615
- * (the SDK stores a corresponding ID Token with every Access Token, and uses the
616
- * scope and audience to look up the ID Token)
617
- *
618
- * @param options
619
424
  */
620
- public async getIdTokenClaims(
621
- options: GetIdTokenClaimsOptions = {}
622
- ): Promise<IdToken | undefined> {
623
- const audience = options.audience || this.options.audience || 'default';
624
- const scope = getUniqueScopes(this.defaultScope, this.scope, options.scope);
425
+ public async getIdTokenClaims(): Promise<IdToken | undefined> {
426
+ const cache = await this._getIdTokenFromCache();
625
427
 
626
- const cache = await this.cacheManager.get(
627
- new CacheKey({
628
- client_id: this.options.client_id,
629
- audience,
630
- scope
631
- })
632
- );
633
-
634
- return cache && cache.decodedToken && cache.decodedToken.claims;
428
+ return cache?.decodedToken?.claims;
635
429
  }
636
430
 
637
431
  /**
@@ -648,9 +442,29 @@ export default class Auth0Client {
648
442
  public async loginWithRedirect<TAppState = any>(
649
443
  options: RedirectLoginOptions<TAppState> = {}
650
444
  ) {
651
- const { redirectMethod, ...urlOptions } = options;
652
- const url = await this.buildAuthorizeUrl(urlOptions);
653
- window.location[redirectMethod || 'assign'](url);
445
+ const { onRedirect, fragment, appState, ...urlOptions } = options;
446
+
447
+ const organizationId =
448
+ urlOptions.authorizationParams?.organization ||
449
+ this.options.authorizationParams.organization;
450
+
451
+ const { url, ...transaction } = await this._prepareAuthorizeUrl(
452
+ urlOptions.authorizationParams
453
+ );
454
+
455
+ this.transactionManager.create({
456
+ ...transaction,
457
+ appState,
458
+ ...(organizationId && { organizationId })
459
+ });
460
+
461
+ const urlWithFragment = fragment ? `${url}#${fragment}` : url;
462
+
463
+ if (onRedirect) {
464
+ await onRedirect(urlWithFragment);
465
+ } else {
466
+ window.location.assign(urlWithFragment);
467
+ }
654
468
  }
655
469
 
656
470
  /**
@@ -697,48 +511,22 @@ export default class Auth0Client {
697
511
  throw new Error('Invalid state');
698
512
  }
699
513
 
700
- const tokenOptions = {
701
- audience: transaction.audience,
702
- scope: transaction.scope,
703
- baseUrl: this.domainUrl,
704
- client_id: this.options.client_id,
705
- code_verifier: transaction.code_verifier,
706
- grant_type: 'authorization_code',
707
- code,
708
- auth0Client: this.options.auth0Client,
709
- useFormData: this.options.useFormData,
710
- timeout: this.httpTimeoutMs
711
- } as OAuthTokenOptions;
712
- // some old versions of the SDK might not have added redirect_uri to the
713
- // transaction, we dont want the key to be set to undefined.
714
- if (undefined !== transaction.redirect_uri) {
715
- tokenOptions.redirect_uri = transaction.redirect_uri;
716
- }
514
+ const organizationId = transaction.organizationId;
515
+ const nonceIn = transaction.nonce;
516
+ const redirect_uri = transaction.redirect_uri;
717
517
 
718
- const authResult = await oauthToken(tokenOptions, this.worker);
719
-
720
- const decodedToken = await this._verifyIdToken(
721
- authResult.id_token,
722
- transaction.nonce,
723
- transaction.organizationId
518
+ await this._requestToken(
519
+ {
520
+ audience: transaction.audience,
521
+ scope: transaction.scope,
522
+ code_verifier: transaction.code_verifier,
523
+ grant_type: 'authorization_code',
524
+ code,
525
+ ...(redirect_uri ? { redirect_uri } : {})
526
+ },
527
+ { nonceIn, organizationId }
724
528
  );
725
529
 
726
- await this.cacheManager.set({
727
- ...authResult,
728
- decodedToken,
729
- audience: transaction.audience,
730
- scope: transaction.scope,
731
- ...(authResult.scope ? { oauthTokenScope: authResult.scope } : null),
732
- client_id: this.options.client_id
733
- });
734
-
735
- this.cookieStorage.save(this.isAuthenticatedCookieName, true, {
736
- daysUntilExpire: this.sessionCheckExpiryDays,
737
- cookieDomain: this.options.cookieDomain
738
- });
739
-
740
- this._processOrgIdHint(decodedToken.claims.org_id);
741
-
742
530
  return {
743
531
  appState: transaction.appState
744
532
  };
@@ -786,11 +574,7 @@ export default class Auth0Client {
786
574
 
787
575
  try {
788
576
  await this.getTokenSilently(options);
789
- } catch (error) {
790
- if (!RECOVERABLE_ERRORS.includes(error.error)) {
791
- throw error;
792
- }
793
- }
577
+ } catch (_) {}
794
578
  }
795
579
 
796
580
  /**
@@ -823,7 +607,7 @@ export default class Auth0Client {
823
607
  * to obtain a new token.
824
608
  *
825
609
  * A new token will be obtained either by opening an iframe or a
826
- * refresh token (if `useRefreshTokens` is `true`)
610
+ * refresh token (if `useRefreshTokens` is `true`).
827
611
 
828
612
  * If iframes are used, opens an iframe with the `/authorize` URL
829
613
  * using the parameters provided as arguments. Random and secure `state`
@@ -832,7 +616,9 @@ export default class Auth0Client {
832
616
  *
833
617
  * If refresh tokens are used, the token endpoint is called directly with the
834
618
  * 'refresh_token' grant. If no refresh token is available to make this call,
835
- * the SDK falls back to using an iframe to the '/authorize' URL.
619
+ * the SDK will only fall back to using an iframe to the '/authorize' URL if
620
+ * the `useRefreshTokensFallback` setting has been set to `true`. By default this
621
+ * setting is `false`.
836
622
  *
837
623
  * This method may use a web worker to perform the token call if the in-memory
838
624
  * cache is used.
@@ -848,35 +634,34 @@ export default class Auth0Client {
848
634
  public async getTokenSilently(
849
635
  options: GetTokenSilentlyOptions = {}
850
636
  ): Promise<string | GetTokenSilentlyVerboseResponse> {
851
- const { ignoreCache, ...getTokenOptions } = {
852
- audience: this.options.audience,
853
- ignoreCache: false,
637
+ options = {
638
+ cacheMode: 'on',
854
639
  ...options,
855
- scope: getUniqueScopes(this.defaultScope, this.scope, options.scope)
640
+ authorizationParams: {
641
+ ...this.options.authorizationParams,
642
+ ...options.authorizationParams,
643
+ scope: getUniqueScopes(this.scope, options.authorizationParams?.scope)
644
+ }
856
645
  };
857
646
 
858
647
  return singlePromise(
859
- () =>
860
- this._getTokenSilently({
861
- ignoreCache,
862
- ...getTokenOptions
863
- }),
864
- `${this.options.client_id}::${getTokenOptions.audience}::${getTokenOptions.scope}`
648
+ () => this._getTokenSilently(options),
649
+ `${this.options.clientId}::${options.authorizationParams.audience}::${options.authorizationParams.scope}`
865
650
  );
866
651
  }
867
652
 
868
653
  private async _getTokenSilently(
869
- options: GetTokenSilentlyOptions = {}
654
+ options: GetTokenSilentlyOptions
870
655
  ): Promise<string | GetTokenSilentlyVerboseResponse> {
871
- const { ignoreCache, ...getTokenOptions } = options;
656
+ const { cacheMode, ...getTokenOptions } = options;
872
657
 
873
658
  // Check the cache before acquiring the lock to avoid the latency of
874
659
  // `lock.acquireLock` when the cache is populated.
875
- if (!ignoreCache) {
660
+ if (cacheMode !== 'off') {
876
661
  const entry = await this._getEntryFromCache({
877
- scope: getTokenOptions.scope,
878
- audience: getTokenOptions.audience || 'default',
879
- client_id: this.options.client_id,
662
+ scope: getTokenOptions.authorizationParams.scope,
663
+ audience: getTokenOptions.authorizationParams.audience || 'default',
664
+ clientId: this.options.clientId,
880
665
  getDetailedEntry: options.detailedResponse
881
666
  });
882
667
 
@@ -885,6 +670,10 @@ export default class Auth0Client {
885
670
  }
886
671
  }
887
672
 
673
+ if (cacheMode === 'cache-only') {
674
+ return;
675
+ }
676
+
888
677
  if (
889
678
  await retryPromise(
890
679
  () => lock.acquireLock(GET_TOKEN_SILENTLY_LOCK_KEY, 5000),
@@ -892,13 +681,16 @@ export default class Auth0Client {
892
681
  )
893
682
  ) {
894
683
  try {
684
+ window.addEventListener('pagehide', this._releaseLockOnPageHide);
685
+
895
686
  // Check the cache a second time, because it may have been populated
896
687
  // by a previous call while this call was waiting to acquire the lock.
897
- if (!ignoreCache) {
688
+ if (cacheMode !== 'off') {
898
689
  const entry = await this._getEntryFromCache({
899
- scope: getTokenOptions.scope,
900
- audience: getTokenOptions.audience || 'default',
901
- client_id: this.options.client_id,
690
+ scope: getTokenOptions.authorizationParams.scope,
691
+ audience:
692
+ getTokenOptions.authorizationParams.audience || 'default',
693
+ clientId: this.options.clientId,
902
694
  getDetailedEntry: options.detailedResponse
903
695
  });
904
696
 
@@ -911,16 +703,6 @@ export default class Auth0Client {
911
703
  ? await this._getTokenUsingRefreshToken(getTokenOptions)
912
704
  : await this._getTokenFromIFrame(getTokenOptions);
913
705
 
914
- await this.cacheManager.set({
915
- client_id: this.options.client_id,
916
- ...authResult
917
- });
918
-
919
- this.cookieStorage.save(this.isAuthenticatedCookieName, true, {
920
- daysUntilExpire: this.sessionCheckExpiryDays,
921
- cookieDomain: this.options.cookieDomain
922
- });
923
-
924
706
  if (options.detailedResponse) {
925
707
  const { id_token, access_token, oauthTokenScope, expires_in } =
926
708
  authResult;
@@ -936,6 +718,7 @@ export default class Auth0Client {
936
718
  return authResult.access_token;
937
719
  } finally {
938
720
  await lock.releaseLock(GET_TOKEN_SILENTLY_LOCK_KEY);
721
+ window.removeEventListener('pagehide', this._releaseLockOnPageHide);
939
722
  }
940
723
  } else {
941
724
  throw new TimeoutError();
@@ -958,13 +741,14 @@ export default class Auth0Client {
958
741
  options: GetTokenWithPopupOptions = {},
959
742
  config: PopupConfigOptions = {}
960
743
  ) {
961
- options.audience = options.audience || this.options.audience;
962
-
963
- options.scope = getUniqueScopes(
964
- this.defaultScope,
965
- this.scope,
966
- options.scope
967
- );
744
+ options = {
745
+ ...options,
746
+ authorizationParams: {
747
+ ...this.options.authorizationParams,
748
+ ...options.authorizationParams,
749
+ scope: getUniqueScopes(this.scope, options.authorizationParams?.scope)
750
+ }
751
+ };
968
752
 
969
753
  config = {
970
754
  ...DEFAULT_POPUP_CONFIG_OPTIONS,
@@ -975,9 +759,9 @@ export default class Auth0Client {
975
759
 
976
760
  const cache = await this.cacheManager.get(
977
761
  new CacheKey({
978
- scope: options.scope,
979
- audience: options.audience || 'default',
980
- client_id: this.options.client_id
762
+ scope: options.authorizationParams.scope,
763
+ audience: options.authorizationParams.audience || 'default',
764
+ clientId: this.options.clientId
981
765
  })
982
766
  );
983
767
 
@@ -1006,88 +790,67 @@ export default class Auth0Client {
1006
790
  * Builds a URL to the logout endpoint using the parameters provided as arguments.
1007
791
  * @param options
1008
792
  */
1009
- public buildLogoutUrl(options: LogoutUrlOptions = {}): string {
1010
- if (options.client_id !== null) {
1011
- options.client_id = options.client_id || this.options.client_id;
793
+ private _buildLogoutUrl(options: LogoutUrlOptions): string {
794
+ if (options.clientId !== null) {
795
+ options.clientId = options.clientId || this.options.clientId;
1012
796
  } else {
1013
- delete options.client_id;
797
+ delete options.clientId;
1014
798
  }
1015
799
 
1016
- const { federated, ...logoutOptions } = options;
800
+ const { federated, ...logoutOptions } = options.logoutParams || {};
1017
801
  const federatedQuery = federated ? `&federated` : '';
1018
- const url = this._url(`/v2/logout?${createQueryParams(logoutOptions)}`);
802
+ const url = this._url(
803
+ `/v2/logout?${createQueryParams({
804
+ clientId: options.clientId,
805
+ ...logoutOptions
806
+ })}`
807
+ );
1019
808
 
1020
809
  return url + federatedQuery;
1021
810
  }
1022
811
 
1023
812
  /**
1024
813
  * ```js
1025
- * auth0.logout();
814
+ * await auth0.logout(options);
1026
815
  * ```
1027
816
  *
1028
817
  * Clears the application session and performs a redirect to `/v2/logout`, using
1029
- * the parameters provided as arguments, to clear the Auth0 session.
1030
- *
1031
- * **Note:** If you are using a custom cache, and specifying `localOnly: true`, and you want to perform actions or read state from the SDK immediately after logout, you should `await` the result of calling `logout`.
818
+ * the parameters provided as arguments, to clear the Auth0 session.
1032
819
  *
1033
820
  * If the `federated` option is specified it also clears the Identity Provider session.
1034
- * If the `localOnly` option is specified, it only clears the application session.
1035
- * It is invalid to set both the `federated` and `localOnly` options to `true`,
1036
- * and an error will be thrown if you do.
1037
821
  * [Read more about how Logout works at Auth0](https://auth0.com/docs/logout).
1038
822
  *
1039
823
  * @param options
1040
824
  */
1041
- public logout(options: LogoutOptions = {}): Promise<void> | void {
1042
- const { localOnly, ...logoutOptions } = options;
1043
-
1044
- if (localOnly && logoutOptions.federated) {
1045
- throw new Error(
1046
- 'It is invalid to set both the `federated` and `localOnly` options to `true`'
1047
- );
1048
- }
825
+ public async logout(options: LogoutOptions = {}): Promise<void> {
826
+ const { onRedirect, ...logoutOptions } = options;
1049
827
 
1050
- const postCacheClear = () => {
1051
- this.cookieStorage.remove(this.orgHintCookieName, { cookieDomain: this.options.cookieDomain });
1052
- this.cookieStorage.remove(this.isAuthenticatedCookieName, { cookieDomain: this.options.cookieDomain });
1053
-
1054
- if (localOnly) {
1055
- return;
1056
- }
1057
-
1058
- const url = this.buildLogoutUrl(logoutOptions);
828
+ await this.cacheManager.clear();
829
+
830
+ this.cookieStorage.remove(this.orgHintCookieName, {
831
+ cookieDomain: this.options.cookieDomain
832
+ });
833
+ this.cookieStorage.remove(this.isAuthenticatedCookieName, {
834
+ cookieDomain: this.options.cookieDomain
835
+ });
836
+ this.userCache.remove(CACHE_KEY_ID_TOKEN_SUFFIX);
1059
837
 
1060
- window.location.assign(url);
1061
- };
838
+ const url = this._buildLogoutUrl(logoutOptions);
1062
839
 
1063
- if (this.options.cache) {
1064
- return this.cacheManager.clear().then(() => postCacheClear());
840
+ if (onRedirect) {
841
+ await onRedirect(url);
1065
842
  } else {
1066
- this.cacheManager.clearSync();
1067
- postCacheClear();
843
+ window.location.assign(url);
1068
844
  }
1069
845
  }
1070
846
 
1071
847
  private async _getTokenFromIFrame(
1072
848
  options: GetTokenSilentlyOptions
1073
849
  ): Promise<GetTokenSilentlyResult> {
1074
- const stateIn = encode(createRandomString());
1075
- const nonceIn = encode(createRandomString());
1076
- const code_verifier = createRandomString();
1077
- const code_challengeBuffer = await sha256(code_verifier);
1078
- const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
1079
-
1080
- const { detailedResponse, ...withoutClientOptions } = options;
1081
-
1082
- const params = this._getParams(
1083
- withoutClientOptions,
1084
- stateIn,
1085
- nonceIn,
1086
- code_challenge,
1087
- options.redirect_uri ||
1088
- this.options.redirect_uri ||
1089
- window.location.origin
1090
- );
850
+ const params: AuthorizationParams = {
851
+ ...options.authorizationParams,
852
+ prompt: 'none'
853
+ };
1091
854
 
1092
855
  const orgIdHint = this.cookieStorage.get<string>(this.orgHintCookieName);
1093
856
 
@@ -1095,11 +858,19 @@ export default class Auth0Client {
1095
858
  params.organization = orgIdHint;
1096
859
  }
1097
860
 
1098
- const url = this._authorizeUrl({
1099
- ...params,
1100
- prompt: 'none',
1101
- response_mode: 'web_message'
1102
- });
861
+ const {
862
+ url,
863
+ state: stateIn,
864
+ nonce: nonceIn,
865
+ code_verifier,
866
+ redirect_uri,
867
+ scope,
868
+ audience
869
+ } = await this._prepareAuthorizeUrl(
870
+ params,
871
+ { response_mode: 'web_message' },
872
+ window.location.origin
873
+ );
1103
874
 
1104
875
  try {
1105
876
  // When a browser is running in a Cross-Origin Isolated context, using iframes is not possible.
@@ -1121,53 +892,30 @@ export default class Auth0Client {
1121
892
  throw new Error('Invalid state');
1122
893
  }
1123
894
 
1124
- const {
1125
- scope,
1126
- audience,
1127
- redirect_uri,
1128
- ignoreCache,
1129
- timeoutInSeconds,
1130
- detailedResponse,
1131
- ...customOptions
1132
- } = options;
1133
-
1134
- const tokenResult = await oauthToken(
895
+ const tokenResult = await this._requestToken(
1135
896
  {
1136
- ...this.customOptions,
1137
- ...customOptions,
1138
- scope,
1139
- audience,
1140
- baseUrl: this.domainUrl,
1141
- client_id: this.options.client_id,
897
+ ...options.authorizationParams,
1142
898
  code_verifier,
1143
899
  code: codeResult.code,
1144
900
  grant_type: 'authorization_code',
1145
- redirect_uri: params.redirect_uri,
1146
- auth0Client: this.options.auth0Client,
1147
- useFormData: this.options.useFormData,
1148
- timeout: customOptions.timeout || this.httpTimeoutMs
1149
- } as OAuthTokenOptions,
1150
- this.worker
1151
- );
1152
-
1153
- const decodedToken = await this._verifyIdToken(
1154
- tokenResult.id_token,
1155
- nonceIn
901
+ redirect_uri,
902
+ timeout: options.authorizationParams.timeout || this.httpTimeoutMs
903
+ },
904
+ {
905
+ nonceIn
906
+ }
1156
907
  );
1157
908
 
1158
- this._processOrgIdHint(decodedToken.claims.org_id);
1159
-
1160
909
  return {
1161
910
  ...tokenResult,
1162
- decodedToken,
1163
- scope: params.scope,
911
+ scope: scope,
1164
912
  oauthTokenScope: tokenResult.scope,
1165
- audience: params.audience || 'default'
913
+ audience: audience
1166
914
  };
1167
915
  } catch (e) {
1168
916
  if (e.error === 'login_required') {
1169
917
  this.logout({
1170
- localOnly: true
918
+ onRedirect: async () => {}
1171
919
  });
1172
920
  }
1173
921
  throw e;
@@ -1177,128 +925,148 @@ export default class Auth0Client {
1177
925
  private async _getTokenUsingRefreshToken(
1178
926
  options: GetTokenSilentlyOptions
1179
927
  ): Promise<GetTokenSilentlyResult> {
1180
- options.scope = getUniqueScopes(
1181
- this.defaultScope,
1182
- this.options.scope,
1183
- options.scope
1184
- );
928
+ options = {
929
+ ...options,
930
+ authorizationParams: {
931
+ ...options.authorizationParams,
932
+ scope: getUniqueScopes(this.scope, options.authorizationParams.scope)
933
+ }
934
+ };
1185
935
 
1186
936
  const cache = await this.cacheManager.get(
1187
937
  new CacheKey({
1188
- scope: options.scope,
1189
- audience: options.audience || 'default',
1190
- client_id: this.options.client_id
938
+ scope: options.authorizationParams.scope,
939
+ audience: options.authorizationParams.audience || 'default',
940
+ clientId: this.options.clientId
1191
941
  })
1192
942
  );
1193
943
 
1194
944
  // If you don't have a refresh token in memory
1195
945
  // and you don't have a refresh token in web worker memory
1196
- // fallback to an iframe.
946
+ // and useRefreshTokensFallback was explicitly enabled
947
+ // fallback to an iframe
1197
948
  if ((!cache || !cache.refresh_token) && !this.worker) {
1198
- if (this.useRefreshTokensFallback) {
949
+ if (this.options.useRefreshTokensFallback) {
1199
950
  return await this._getTokenFromIFrame(options);
1200
951
  }
1201
952
 
1202
953
  throw new MissingRefreshTokenError(
1203
- options.audience || 'default',
1204
- options.scope
954
+ options.authorizationParams.audience || 'default',
955
+ options.authorizationParams.scope
1205
956
  );
1206
957
  }
1207
958
 
1208
959
  const redirect_uri =
1209
- options.redirect_uri ||
1210
- this.options.redirect_uri ||
960
+ options.authorizationParams.redirect_uri ||
961
+ this.options.authorizationParams.redirect_uri ||
1211
962
  window.location.origin;
1212
963
 
1213
- let tokenResult: TokenEndpointResponse;
1214
-
1215
- const {
1216
- scope,
1217
- audience,
1218
- ignoreCache,
1219
- timeoutInSeconds,
1220
- detailedResponse,
1221
- ...customOptions
1222
- } = options;
1223
-
1224
964
  const timeout =
1225
965
  typeof options.timeoutInSeconds === 'number'
1226
966
  ? options.timeoutInSeconds * 1000
1227
967
  : null;
1228
968
 
1229
969
  try {
1230
- tokenResult = await oauthToken(
1231
- {
1232
- ...this.customOptions,
1233
- ...customOptions,
1234
- audience,
1235
- scope,
1236
- baseUrl: this.domainUrl,
1237
- client_id: this.options.client_id,
1238
- grant_type: 'refresh_token',
1239
- refresh_token: cache && cache.refresh_token,
1240
- redirect_uri,
1241
- ...(timeout && { timeout }),
1242
- auth0Client: this.options.auth0Client,
1243
- useFormData: this.options.useFormData,
1244
- timeout: this.httpTimeoutMs
1245
- } as RefreshTokenOptions,
1246
- this.worker
1247
- );
970
+ const tokenResult = await this._requestToken({
971
+ ...options.authorizationParams,
972
+ grant_type: 'refresh_token',
973
+ refresh_token: cache && cache.refresh_token,
974
+ redirect_uri,
975
+ ...(timeout && { timeout })
976
+ });
977
+
978
+ return {
979
+ ...tokenResult,
980
+ scope: options.authorizationParams.scope,
981
+ oauthTokenScope: tokenResult.scope,
982
+ audience: options.authorizationParams.audience || 'default'
983
+ };
1248
984
  } catch (e) {
1249
985
  if (
1250
986
  // The web worker didn't have a refresh token in memory so
1251
987
  // fallback to an iframe.
1252
988
  (e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 ||
1253
- // A refresh token was found, but is it no longer valid.
1254
- // Fallback to an iframe.
989
+ // A refresh token was found, but is it no longer valid
990
+ // and useRefreshTokensFallback is explicitly enabled. Fallback to an iframe.
1255
991
  (e.message &&
1256
992
  e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)) &&
1257
- this.useRefreshTokensFallback
993
+ this.options.useRefreshTokensFallback
1258
994
  ) {
1259
995
  return await this._getTokenFromIFrame(options);
1260
996
  }
1261
997
 
1262
998
  throw e;
1263
999
  }
1000
+ }
1264
1001
 
1265
- const decodedToken = await this._verifyIdToken(tokenResult.id_token);
1002
+ private async _saveEntryInCache(entry: CacheEntry) {
1003
+ const { id_token, decodedToken, ...entryWithoutIdToken } = entry;
1266
1004
 
1267
- return {
1268
- ...tokenResult,
1269
- decodedToken,
1270
- scope: options.scope,
1271
- oauthTokenScope: tokenResult.scope,
1272
- audience: options.audience || 'default'
1273
- };
1005
+ this.userCache.set(CACHE_KEY_ID_TOKEN_SUFFIX, {
1006
+ id_token,
1007
+ decodedToken
1008
+ });
1009
+
1010
+ await this.cacheManager.setIdToken(
1011
+ this.options.clientId,
1012
+ entry.id_token,
1013
+ entry.decodedToken
1014
+ );
1015
+
1016
+ await this.cacheManager.set(entryWithoutIdToken);
1017
+ }
1018
+
1019
+ private async _getIdTokenFromCache() {
1020
+ const audience = this.options.authorizationParams.audience || 'default';
1021
+
1022
+ const cache = await this.cacheManager.getIdToken(
1023
+ new CacheKey({
1024
+ clientId: this.options.clientId,
1025
+ audience,
1026
+ scope: this.scope
1027
+ })
1028
+ );
1029
+
1030
+ const currentCache = this.userCache.get<IdTokenEntry>(
1031
+ CACHE_KEY_ID_TOKEN_SUFFIX
1032
+ ) as IdTokenEntry;
1033
+
1034
+ // If the id_token in the cache matches the value we previously cached in memory return the in-memory
1035
+ // value so that object comparison will work
1036
+ if (cache && cache.id_token === currentCache?.id_token) {
1037
+ return currentCache;
1038
+ }
1039
+
1040
+ this.userCache.set(CACHE_KEY_ID_TOKEN_SUFFIX, cache);
1041
+ return cache;
1274
1042
  }
1275
1043
 
1276
1044
  private async _getEntryFromCache({
1277
1045
  scope,
1278
1046
  audience,
1279
- client_id,
1047
+ clientId,
1280
1048
  getDetailedEntry = false
1281
1049
  }: {
1282
1050
  scope: string;
1283
1051
  audience: string;
1284
- client_id: string;
1052
+ clientId: string;
1285
1053
  getDetailedEntry?: boolean;
1286
1054
  }) {
1287
1055
  const entry = await this.cacheManager.get(
1288
1056
  new CacheKey({
1289
1057
  scope,
1290
1058
  audience,
1291
- client_id
1059
+ clientId
1292
1060
  }),
1293
1061
  60 // get a new token if within 60 seconds of expiring
1294
1062
  );
1295
1063
 
1296
1064
  if (entry && entry.access_token) {
1297
1065
  if (getDetailedEntry) {
1298
- const { id_token, access_token, oauthTokenScope, expires_in } = entry;
1299
-
1066
+ const { access_token, oauthTokenScope, expires_in } = entry;
1067
+ const cache = await this._getIdTokenFromCache();
1300
1068
  return {
1301
- id_token,
1069
+ id_token: cache?.id_token,
1302
1070
  access_token,
1303
1071
  ...(oauthTokenScope ? { scope: oauthTokenScope } : null),
1304
1072
  expires_in
@@ -1308,4 +1076,81 @@ export default class Auth0Client {
1308
1076
  return entry.access_token;
1309
1077
  }
1310
1078
  }
1079
+
1080
+ /**
1081
+ * Releases any lock acquired by the current page that's not released yet
1082
+ *
1083
+ * Get's called on the `pagehide` event.
1084
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
1085
+ */
1086
+ private _releaseLockOnPageHide = async () => {
1087
+ await lock.releaseLock(GET_TOKEN_SILENTLY_LOCK_KEY);
1088
+
1089
+ window.removeEventListener('pagehide', this._releaseLockOnPageHide);
1090
+ };
1091
+
1092
+ private async _requestToken(
1093
+ options: PKCERequestTokenOptions | RefreshTokenRequestTokenOptions,
1094
+ additionalParameters?: RequestTokenAdditionalParameters
1095
+ ) {
1096
+ const { nonceIn, organizationId } = additionalParameters || {};
1097
+ const authResult = await oauthToken(
1098
+ {
1099
+ baseUrl: this.domainUrl,
1100
+ client_id: this.options.clientId,
1101
+ auth0Client: this.options.auth0Client,
1102
+ useFormData: this.options.useFormData,
1103
+ timeout: this.httpTimeoutMs,
1104
+ ...options
1105
+ },
1106
+ this.worker
1107
+ );
1108
+
1109
+ const decodedToken = await this._verifyIdToken(
1110
+ authResult.id_token,
1111
+ nonceIn,
1112
+ organizationId
1113
+ );
1114
+
1115
+ await this._saveEntryInCache({
1116
+ ...authResult,
1117
+ decodedToken,
1118
+ scope: options.scope,
1119
+ audience: options.audience || 'default',
1120
+ ...(authResult.scope ? { oauthTokenScope: authResult.scope } : null),
1121
+ client_id: this.options.clientId
1122
+ });
1123
+
1124
+ this.cookieStorage.save(this.isAuthenticatedCookieName, true, {
1125
+ daysUntilExpire: this.sessionCheckExpiryDays,
1126
+ cookieDomain: this.options.cookieDomain
1127
+ });
1128
+
1129
+ this._processOrgIdHint(decodedToken.claims.org_id);
1130
+
1131
+ return { ...authResult, decodedToken };
1132
+ }
1133
+ }
1134
+
1135
+ interface BaseRequestTokenOptions {
1136
+ audience?: string;
1137
+ scope?: string;
1138
+ timeout?: number;
1139
+ redirect_uri?: string;
1140
+ }
1141
+
1142
+ interface PKCERequestTokenOptions extends BaseRequestTokenOptions {
1143
+ code: string;
1144
+ grant_type: 'authorization_code';
1145
+ code_verifier: string;
1146
+ }
1147
+
1148
+ interface RefreshTokenRequestTokenOptions extends BaseRequestTokenOptions {
1149
+ grant_type: 'refresh_token';
1150
+ refresh_token: string;
1151
+ }
1152
+
1153
+ interface RequestTokenAdditionalParameters {
1154
+ nonceIn?: string;
1155
+ organizationId?: string;
1311
1156
  }