@auth0/auth0-spa-js 2.11.2 → 2.11.3

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.
@@ -4,10 +4,22 @@ import { Auth0ClientOptions, AuthorizationParams, AuthorizeOptions, ClientAuthor
4
4
  * @ignore
5
5
  */
6
6
  export declare const GET_TOKEN_SILENTLY_LOCK_KEY = "auth0.lock.getTokenSilently";
7
+ /**
8
+ * @ignore
9
+ */
10
+ export declare const GET_TOKEN_FROM_IFRAME_LOCK_KEY = "auth0.lock.getTokenFromIFrame";
7
11
  /**
8
12
  * @ignore
9
13
  */
10
14
  export declare const buildGetTokenSilentlyLockKey: (clientId: string, audience: string) => string;
15
+ /**
16
+ * @ignore
17
+ * Builds a global lock key for iframe-based authentication flows.
18
+ * This ensures only one iframe authorization request runs at a time per client,
19
+ * preventing "Invalid state" errors from concurrent iframe requests overwriting
20
+ * each other's state in the Auth0 session.
21
+ */
22
+ export declare const buildIframeLockKey: (clientId: string) => string;
11
23
  /**
12
24
  * @ignore
13
25
  */
@@ -1,2 +1,2 @@
1
- declare const _default: "2.11.2";
1
+ declare const _default: "2.11.3";
2
2
  export default _default;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "@auth0/auth0-spa-js",
4
4
  "description": "Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE",
5
5
  "license": "MIT",
6
- "version": "2.11.2",
6
+ "version": "2.11.3",
7
7
  "main": "dist/lib/auth0-spa-js.cjs.js",
8
8
  "types": "dist/typings/index.d.ts",
9
9
  "module": "dist/auth0-spa-js.production.esm.js",
@@ -98,6 +98,7 @@ import {
98
98
  cacheFactory,
99
99
  getAuthorizeParams,
100
100
  buildGetTokenSilentlyLockKey,
101
+ buildIframeLockKey,
101
102
  OLD_IS_AUTHENTICATED_COOKIE_NAME,
102
103
  patchOpenUrlWithOnRedirect,
103
104
  getScopeToRequest,
@@ -1023,87 +1024,105 @@ export class Auth0Client {
1023
1024
  authorizationParams: AuthorizationParams & { scope: string };
1024
1025
  }
1025
1026
  ): Promise<GetTokenSilentlyResult> {
1026
- const params: AuthorizationParams & { scope: string } = {
1027
- ...options.authorizationParams,
1028
- prompt: 'none'
1029
- };
1030
-
1031
- const orgHint = this.cookieStorage.get<string>(this.orgHintCookieName);
1027
+ const iframeLockKey = buildIframeLockKey(this.options.clientId);
1028
+
1029
+ // Acquire global iframe lock to serialize iframe authorization flows.
1030
+ // This is necessary because the SDK does not support multiple simultaneous transactions.
1031
+ // Since https://github.com/auth0/auth0-spa-js/pull/1408, when calling
1032
+ // `getTokenSilently()`, the global locking will lock per `audience` instead of locking
1033
+ // only per `client_id`.
1034
+ // This means that calls for different audiences would happen in parallel, which does
1035
+ // not work when using silent authentication (prompt=none) from within the SDK, as that
1036
+ // relies on the same transaction context as a top-level `loginWithRedirect`.
1037
+ // To resolve that, we add a second-level locking that locks only the iframe calls in
1038
+ // the same way as was done before https://github.com/auth0/auth0-spa-js/pull/1408.
1039
+ if (await retryPromise(() => lock.acquireLock(iframeLockKey, 5000), 10)) {
1040
+ try {
1041
+ const params: AuthorizationParams & { scope: string } = {
1042
+ ...options.authorizationParams,
1043
+ prompt: 'none'
1044
+ };
1032
1045
 
1033
- if (orgHint && !params.organization) {
1034
- params.organization = orgHint;
1035
- }
1046
+ const orgHint = this.cookieStorage.get<string>(this.orgHintCookieName);
1036
1047
 
1037
- const {
1038
- url,
1039
- state: stateIn,
1040
- nonce: nonceIn,
1041
- code_verifier,
1042
- redirect_uri,
1043
- scope,
1044
- audience
1045
- } = await this._prepareAuthorizeUrl(
1046
- params,
1047
- { response_mode: 'web_message' },
1048
- window.location.origin
1049
- );
1048
+ if (orgHint && !params.organization) {
1049
+ params.organization = orgHint;
1050
+ }
1050
1051
 
1051
- try {
1052
- // When a browser is running in a Cross-Origin Isolated context, using iframes is not possible.
1053
- // It doesn't throw an error but times out instead, so we should exit early and inform the user about the reason.
1054
- // https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated
1055
- if ((window as any).crossOriginIsolated) {
1056
- throw new GenericError(
1057
- 'login_required',
1058
- 'The application is running in a Cross-Origin Isolated context, silently retrieving a token without refresh token is not possible.'
1052
+ const {
1053
+ url,
1054
+ state: stateIn,
1055
+ nonce: nonceIn,
1056
+ code_verifier,
1057
+ redirect_uri,
1058
+ scope,
1059
+ audience
1060
+ } = await this._prepareAuthorizeUrl(
1061
+ params,
1062
+ { response_mode: 'web_message' },
1063
+ window.location.origin
1059
1064
  );
1060
- }
1061
1065
 
1062
- const authorizeTimeout =
1063
- options.timeoutInSeconds || this.options.authorizeTimeoutInSeconds;
1066
+ // When a browser is running in a Cross-Origin Isolated context, using iframes is not possible.
1067
+ // It doesn't throw an error but times out instead, so we should exit early and inform the user about the reason.
1068
+ // https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated
1069
+ if ((window as any).crossOriginIsolated) {
1070
+ throw new GenericError(
1071
+ 'login_required',
1072
+ 'The application is running in a Cross-Origin Isolated context, silently retrieving a token without refresh token is not possible.'
1073
+ );
1074
+ }
1064
1075
 
1065
- // Extract origin from domainUrl, fallback to domainUrl if URL parsing fails
1066
- let eventOrigin: string;
1067
- try {
1068
- eventOrigin = new URL(this.domainUrl).origin;
1069
- } catch {
1070
- eventOrigin = this.domainUrl;
1071
- }
1076
+ const authorizeTimeout =
1077
+ options.timeoutInSeconds || this.options.authorizeTimeoutInSeconds;
1072
1078
 
1073
- const codeResult = await runIframe(url, eventOrigin, authorizeTimeout);
1079
+ // Extract origin from domainUrl, fallback to domainUrl if URL parsing fails
1080
+ let eventOrigin: string;
1081
+ try {
1082
+ eventOrigin = new URL(this.domainUrl).origin;
1083
+ } catch {
1084
+ eventOrigin = this.domainUrl;
1085
+ }
1074
1086
 
1075
- if (stateIn !== codeResult.state) {
1076
- throw new GenericError('state_mismatch', 'Invalid state');
1077
- }
1087
+ const codeResult = await runIframe(url, eventOrigin, authorizeTimeout);
1078
1088
 
1079
- const tokenResult = await this._requestToken(
1080
- {
1081
- ...options.authorizationParams,
1082
- code_verifier,
1083
- code: codeResult.code as string,
1084
- grant_type: 'authorization_code',
1085
- redirect_uri,
1086
- timeout: options.authorizationParams.timeout || this.httpTimeoutMs
1087
- },
1088
- {
1089
- nonceIn,
1090
- organization: params.organization
1089
+ if (stateIn !== codeResult.state) {
1090
+ throw new GenericError('state_mismatch', 'Invalid state');
1091
1091
  }
1092
- );
1093
1092
 
1094
- return {
1095
- ...tokenResult,
1096
- scope: scope,
1097
- oauthTokenScope: tokenResult.scope,
1098
- audience: audience
1099
- };
1100
- } catch (e) {
1101
- if (e.error === 'login_required') {
1102
- this.logout({
1103
- openUrl: false
1104
- });
1093
+ const tokenResult = await this._requestToken(
1094
+ {
1095
+ ...options.authorizationParams,
1096
+ code_verifier,
1097
+ code: codeResult.code as string,
1098
+ grant_type: 'authorization_code',
1099
+ redirect_uri,
1100
+ timeout: options.authorizationParams.timeout || this.httpTimeoutMs
1101
+ },
1102
+ {
1103
+ nonceIn,
1104
+ organization: params.organization
1105
+ }
1106
+ );
1107
+
1108
+ return {
1109
+ ...tokenResult,
1110
+ scope: scope,
1111
+ oauthTokenScope: tokenResult.scope,
1112
+ audience: audience
1113
+ };
1114
+ } catch (e) {
1115
+ if (e.error === 'login_required') {
1116
+ this.logout({
1117
+ openUrl: false
1118
+ });
1119
+ }
1120
+ throw e;
1121
+ } finally {
1122
+ await lock.releaseLock(iframeLockKey);
1105
1123
  }
1106
- throw e;
1124
+ } else {
1125
+ throw new TimeoutError();
1107
1126
  }
1108
1127
  }
1109
1128
 
@@ -1169,7 +1188,7 @@ export class Auth0Client {
1169
1188
 
1170
1189
  // If is refreshed with MRRT, we update all entries that have the old
1171
1190
  // refresh_token with the new one if the server responded with one
1172
- if (tokenResult.refresh_token && this.options.useMrrt && cache?.refresh_token) {
1191
+ if (tokenResult.refresh_token && cache?.refresh_token) {
1173
1192
  await this.cacheManager.updateEntry(
1174
1193
  cache.refresh_token,
1175
1194
  tokenResult.refresh_token
@@ -13,6 +13,11 @@ import { scopesToRequest } from './scope';
13
13
  */
14
14
  export const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently';
15
15
 
16
+ /**
17
+ * @ignore
18
+ */
19
+ export const GET_TOKEN_FROM_IFRAME_LOCK_KEY = 'auth0.lock.getTokenFromIFrame';
20
+
16
21
  /**
17
22
  * @ignore
18
23
  */
@@ -21,6 +26,16 @@ export const buildGetTokenSilentlyLockKey = (
21
26
  audience: string
22
27
  ) => `${GET_TOKEN_SILENTLY_LOCK_KEY}.${clientId}.${audience}`;
23
28
 
29
+ /**
30
+ * @ignore
31
+ * Builds a global lock key for iframe-based authentication flows.
32
+ * This ensures only one iframe authorization request runs at a time per client,
33
+ * preventing "Invalid state" errors from concurrent iframe requests overwriting
34
+ * each other's state in the Auth0 session.
35
+ */
36
+ export const buildIframeLockKey = (clientId: string) =>
37
+ `${GET_TOKEN_FROM_IFRAME_LOCK_KEY}.${clientId}`;
38
+
24
39
  /**
25
40
  * @ignore
26
41
  */
@@ -91,7 +91,7 @@ export class CacheManager {
91
91
  // To refresh using MRRT we need to send a request to the server
92
92
  // If cacheMode is 'cache-only', this will make us unable to call the server
93
93
  // so it won't be needed to find a valid refresh token
94
- if (!matchedKey && useMrrt && cacheMode !== 'cache-only') {
94
+ if (!wrappedEntry && useMrrt && cacheMode !== 'cache-only') {
95
95
  return this.getEntryWithRefreshToken(cacheKey, keys);
96
96
  }
97
97
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export default '2.11.2';
1
+ export default '2.11.3';