@auth0/auth0-spa-js 2.14.0 → 2.16.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.
- package/README.md +1 -1
- package/dist/auth0-spa-js.development.js +698 -641
- package/dist/auth0-spa-js.development.js.map +1 -1
- package/dist/auth0-spa-js.production.esm.js +1 -1
- package/dist/auth0-spa-js.production.esm.js.map +1 -1
- package/dist/auth0-spa-js.production.js +1 -1
- package/dist/auth0-spa-js.production.js.map +1 -1
- package/dist/lib/auth0-spa-js.cjs.js +888 -827
- package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
- package/dist/typings/Auth0Client.d.ts +11 -8
- package/dist/typings/cache/cache-manager.d.ts +9 -5
- package/dist/typings/global.d.ts +21 -0
- package/dist/typings/lock.d.ts +32 -0
- package/dist/typings/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Auth0Client.ts +144 -124
- package/src/cache/cache-manager.ts +11 -11
- package/src/global.ts +22 -0
- package/src/lock.ts +141 -0
- package/src/version.ts +1 -1
|
@@ -9,6 +9,7 @@ import { MfaApiClient } from './mfa';
|
|
|
9
9
|
export declare class Auth0Client {
|
|
10
10
|
private readonly transactionManager;
|
|
11
11
|
private readonly cacheManager;
|
|
12
|
+
private readonly lockManager;
|
|
12
13
|
private readonly domainUrl;
|
|
13
14
|
private readonly tokenIssuer;
|
|
14
15
|
private readonly scope;
|
|
@@ -33,7 +34,6 @@ export declare class Auth0Client {
|
|
|
33
34
|
*/
|
|
34
35
|
readonly mfa: MfaApiClient;
|
|
35
36
|
private worker?;
|
|
36
|
-
private readonly activeLockKeys;
|
|
37
37
|
private readonly authJsClient;
|
|
38
38
|
private readonly defaultOptions;
|
|
39
39
|
constructor(options: Auth0ClientOptions);
|
|
@@ -185,6 +185,16 @@ export declare class Auth0Client {
|
|
|
185
185
|
*/
|
|
186
186
|
getTokenSilently(options?: GetTokenSilentlyOptions): Promise<string>;
|
|
187
187
|
private _getTokenSilently;
|
|
188
|
+
/**
|
|
189
|
+
* Checks if an error should be handled by the interactive error handler.
|
|
190
|
+
* Currently only handles mfa_required; extensible for future error types.
|
|
191
|
+
*/
|
|
192
|
+
private _isInteractiveError;
|
|
193
|
+
/**
|
|
194
|
+
* Handles MFA errors by opening a popup to complete authentication,
|
|
195
|
+
* then reads the resulting token from cache.
|
|
196
|
+
*/
|
|
197
|
+
private _handleInteractiveErrorWithPopup;
|
|
188
198
|
/**
|
|
189
199
|
* ```js
|
|
190
200
|
* const token = await auth0.getTokenWithPopup(options);
|
|
@@ -236,13 +246,6 @@ export declare class Auth0Client {
|
|
|
236
246
|
private _saveEntryInCache;
|
|
237
247
|
private _getIdTokenFromCache;
|
|
238
248
|
private _getEntryFromCache;
|
|
239
|
-
/**
|
|
240
|
-
* Releases any locks acquired by the current page that are not released yet
|
|
241
|
-
*
|
|
242
|
-
* Get's called on the `pagehide` event.
|
|
243
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
|
|
244
|
-
*/
|
|
245
|
-
private _releaseLockOnPageHide;
|
|
246
249
|
private _requestToken;
|
|
247
250
|
/**
|
|
248
251
|
* ```js
|
|
@@ -43,10 +43,14 @@ export declare class CacheManager {
|
|
|
43
43
|
*/
|
|
44
44
|
private getEntryWithRefreshToken;
|
|
45
45
|
/**
|
|
46
|
-
* Updates in
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
46
|
+
* Updates the refresh token in all cache entries that contain the old refresh token.
|
|
47
|
+
*
|
|
48
|
+
* When a refresh token is rotated, multiple cache entries (for different audiences/scopes)
|
|
49
|
+
* may share the same refresh token. This method propagates the new refresh token to all
|
|
50
|
+
* matching entries.
|
|
51
|
+
*
|
|
52
|
+
* @param oldRefreshToken The refresh token that was used and is now invalid
|
|
53
|
+
* @param newRefreshToken The new refresh token received from the server
|
|
54
|
+
*/
|
|
51
55
|
updateEntry(oldRefreshToken: string, newRefreshToken: string): Promise<void>;
|
|
52
56
|
}
|
package/dist/typings/global.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ICache } from './cache';
|
|
2
2
|
import type { Dpop } from './dpop/dpop';
|
|
3
3
|
import { CompleteResponse } from './MyAccountApiClient';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration option for automatic interactive error handling.
|
|
6
|
+
*
|
|
7
|
+
* - `'popup'`: SDK automatically opens Universal Login popup on MFA error
|
|
8
|
+
*/
|
|
9
|
+
export type InteractiveErrorHandler = 'popup';
|
|
4
10
|
export interface AuthorizationParams {
|
|
5
11
|
/**
|
|
6
12
|
* - `'page'`: displays the UI with a full page view
|
|
@@ -262,6 +268,21 @@ export interface Auth0ClientOptions {
|
|
|
262
268
|
* The default setting is `false`.
|
|
263
269
|
*/
|
|
264
270
|
useDpop?: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Configures automatic handling of interactive authentication errors.
|
|
273
|
+
*
|
|
274
|
+
* When set, the SDK intercepts `mfa_required` errors from `getTokenSilently()`
|
|
275
|
+
* and handles them automatically instead of throwing to the caller.
|
|
276
|
+
*
|
|
277
|
+
* - `'popup'`: Opens Universal Login in a popup to complete MFA.
|
|
278
|
+
* The original `authorizationParams` (audience, scope) are preserved.
|
|
279
|
+
* On success, the token is returned. On failure, popup errors are thrown.
|
|
280
|
+
*
|
|
281
|
+
* This option only affects `getTokenSilently()`. Other methods are not affected.
|
|
282
|
+
*
|
|
283
|
+
* @default undefined (MFA errors are thrown to the caller)
|
|
284
|
+
*/
|
|
285
|
+
interactiveErrorHandler?: InteractiveErrorHandler;
|
|
265
286
|
/**
|
|
266
287
|
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
|
|
267
288
|
* defined by Auth0 or custom parameters that you define.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock manager abstraction for cross-tab synchronization.
|
|
3
|
+
* Supports both modern Web Locks API and legacy localStorage-based locking.
|
|
4
|
+
*/
|
|
5
|
+
/** Lock manager interface - callback pattern ensures automatic lock release */
|
|
6
|
+
export interface ILockManager {
|
|
7
|
+
/**
|
|
8
|
+
* Run callback while holding a lock.
|
|
9
|
+
* Lock is automatically released when callback completes or throws.
|
|
10
|
+
*
|
|
11
|
+
* @param key - Lock identifier
|
|
12
|
+
* @param timeout - Maximum time to wait for lock acquisition (ms)
|
|
13
|
+
* @param callback - Function to execute while holding the lock
|
|
14
|
+
* @returns Promise resolving to callback's return value
|
|
15
|
+
* @throws Error if lock cannot be acquired within timeout
|
|
16
|
+
*/
|
|
17
|
+
runWithLock<T>(key: string, timeout: number, callback: () => Promise<T>): Promise<T>;
|
|
18
|
+
}
|
|
19
|
+
/** Web Locks API implementation - true mutex with OS-level queuing */
|
|
20
|
+
export declare class WebLocksApiManager implements ILockManager {
|
|
21
|
+
runWithLock<T>(key: string, timeout: number, callback: () => Promise<T>): Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
/** Legacy localStorage-based locking with retry logic for older browsers */
|
|
24
|
+
export declare class LegacyLockManager implements ILockManager {
|
|
25
|
+
private lock;
|
|
26
|
+
private activeLocks;
|
|
27
|
+
private pagehideHandler;
|
|
28
|
+
constructor();
|
|
29
|
+
runWithLock<T>(key: string, timeout: number, callback: () => Promise<T>): Promise<T>;
|
|
30
|
+
}
|
|
31
|
+
export declare function getLockManager(): ILockManager;
|
|
32
|
+
export declare function resetLockManager(): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.16.0";
|
|
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.
|
|
6
|
+
"version": "2.16.0",
|
|
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",
|
package/src/Auth0Client.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Lock from 'browser-tabs-lock';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
createQueryParams,
|
|
5
3
|
runPopup,
|
|
@@ -17,6 +15,8 @@ import {
|
|
|
17
15
|
stripAuth0Client
|
|
18
16
|
} from './utils';
|
|
19
17
|
|
|
18
|
+
import { getLockManager, type ILockManager } from './lock';
|
|
19
|
+
|
|
20
20
|
import { oauthToken } from './api';
|
|
21
21
|
|
|
22
22
|
import { injectDefaultScopes, scopesToRequest } from './scope';
|
|
@@ -131,17 +131,13 @@ type GetTokenSilentlyResult = TokenEndpointResponse & {
|
|
|
131
131
|
audience: string;
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
/**
|
|
135
|
-
* @ignore
|
|
136
|
-
*/
|
|
137
|
-
const lock = new Lock();
|
|
138
|
-
|
|
139
134
|
/**
|
|
140
135
|
* Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce).
|
|
141
136
|
*/
|
|
142
137
|
export class Auth0Client {
|
|
143
138
|
private readonly transactionManager: TransactionManager;
|
|
144
139
|
private readonly cacheManager: CacheManager;
|
|
140
|
+
private readonly lockManager: ILockManager;
|
|
145
141
|
private readonly domainUrl: string;
|
|
146
142
|
private readonly tokenIssuer: string;
|
|
147
143
|
private readonly scope: Record<string, string>;
|
|
@@ -170,7 +166,6 @@ export class Auth0Client {
|
|
|
170
166
|
public readonly mfa: MfaApiClient;
|
|
171
167
|
|
|
172
168
|
private worker?: Worker;
|
|
173
|
-
private readonly activeLockKeys: Set<string> = new Set();
|
|
174
169
|
private readonly authJsClient: Auth0AuthJsClient;
|
|
175
170
|
|
|
176
171
|
private readonly defaultOptions: Partial<Auth0ClientOptions> = {
|
|
@@ -193,6 +188,8 @@ export class Auth0Client {
|
|
|
193
188
|
|
|
194
189
|
typeof window !== 'undefined' && validateCrypto();
|
|
195
190
|
|
|
191
|
+
this.lockManager = getLockManager();
|
|
192
|
+
|
|
196
193
|
if (options.cache && options.cacheLocation) {
|
|
197
194
|
console.warn(
|
|
198
195
|
'Both `cache` and `cacheLocation` options have been specified in the Auth0Client configuration; ignoring `cacheLocation` and using `cache`.'
|
|
@@ -886,20 +883,15 @@ export class Auth0Client {
|
|
|
886
883
|
getTokenOptions.authorizationParams.audience || 'default'
|
|
887
884
|
);
|
|
888
885
|
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
// Add event listener only if this is the first active lock
|
|
893
|
-
if (this.activeLockKeys.size === 1) {
|
|
894
|
-
window.addEventListener('pagehide', this._releaseLockOnPageHide);
|
|
895
|
-
}
|
|
896
|
-
try {
|
|
886
|
+
try {
|
|
887
|
+
return await this.lockManager.runWithLock(lockKey, 5000, async () => {
|
|
897
888
|
// Check the cache a second time, because it may have been populated
|
|
898
889
|
// by a previous call while this call was waiting to acquire the lock.
|
|
899
890
|
if (cacheMode !== 'off') {
|
|
900
891
|
const entry = await this._getEntryFromCache({
|
|
901
892
|
scope: getTokenOptions.authorizationParams.scope,
|
|
902
|
-
audience:
|
|
893
|
+
audience:
|
|
894
|
+
getTokenOptions.authorizationParams.audience || DEFAULT_AUDIENCE,
|
|
903
895
|
clientId: this.options.clientId
|
|
904
896
|
});
|
|
905
897
|
|
|
@@ -912,13 +904,8 @@ export class Auth0Client {
|
|
|
912
904
|
? await this._getTokenUsingRefreshToken(getTokenOptions)
|
|
913
905
|
: await this._getTokenFromIFrame(getTokenOptions);
|
|
914
906
|
|
|
915
|
-
const {
|
|
916
|
-
|
|
917
|
-
token_type,
|
|
918
|
-
access_token,
|
|
919
|
-
oauthTokenScope,
|
|
920
|
-
expires_in
|
|
921
|
-
} = authResult;
|
|
907
|
+
const { id_token, token_type, access_token, oauthTokenScope, expires_in } =
|
|
908
|
+
authResult;
|
|
922
909
|
|
|
923
910
|
return {
|
|
924
911
|
id_token,
|
|
@@ -927,16 +914,60 @@ export class Auth0Client {
|
|
|
927
914
|
...(oauthTokenScope ? { scope: oauthTokenScope } : null),
|
|
928
915
|
expires_in
|
|
929
916
|
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
window.removeEventListener('pagehide', this._releaseLockOnPageHide);
|
|
936
|
-
}
|
|
917
|
+
});
|
|
918
|
+
} catch (error) {
|
|
919
|
+
// Lock is already released - safe to open popup
|
|
920
|
+
if (this._isInteractiveError(error) && this.options.interactiveErrorHandler === 'popup') {
|
|
921
|
+
return await this._handleInteractiveErrorWithPopup(getTokenOptions);
|
|
937
922
|
}
|
|
938
|
-
|
|
939
|
-
|
|
923
|
+
throw error;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Checks if an error should be handled by the interactive error handler.
|
|
929
|
+
* Currently only handles mfa_required; extensible for future error types.
|
|
930
|
+
*/
|
|
931
|
+
private _isInteractiveError(error: unknown): error is MfaRequiredError {
|
|
932
|
+
return error instanceof MfaRequiredError;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Handles MFA errors by opening a popup to complete authentication,
|
|
937
|
+
* then reads the resulting token from cache.
|
|
938
|
+
*/
|
|
939
|
+
private async _handleInteractiveErrorWithPopup(
|
|
940
|
+
options: GetTokenSilentlyOptions & {
|
|
941
|
+
authorizationParams: AuthorizationParams & { scope: string };
|
|
942
|
+
}
|
|
943
|
+
): Promise<GetTokenSilentlyVerboseResponse> {
|
|
944
|
+
try {
|
|
945
|
+
await this.loginWithPopup({
|
|
946
|
+
authorizationParams: options.authorizationParams
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
const entry = await this._getEntryFromCache({
|
|
950
|
+
scope: options.authorizationParams.scope,
|
|
951
|
+
audience:
|
|
952
|
+
options.authorizationParams.audience || DEFAULT_AUDIENCE,
|
|
953
|
+
clientId: this.options.clientId
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
if (!entry) {
|
|
957
|
+
throw new GenericError(
|
|
958
|
+
'interactive_handler_cache_miss',
|
|
959
|
+
'Token not found in cache after interactive authentication'
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return entry;
|
|
964
|
+
} catch (error) {
|
|
965
|
+
// Expected errors (all GenericError subclasses):
|
|
966
|
+
// - PopupCancelledError: user closed the popup before completing login
|
|
967
|
+
// - PopupTimeoutError: popup did not complete within the allowed time
|
|
968
|
+
// - PopupOpenError: popup could not be opened (e.g. blocked by browser)
|
|
969
|
+
// - GenericError: authentication or cache miss errors
|
|
970
|
+
throw error;
|
|
940
971
|
}
|
|
941
972
|
}
|
|
942
973
|
|
|
@@ -1088,93 +1119,99 @@ export class Auth0Client {
|
|
|
1088
1119
|
// relies on the same transaction context as a top-level `loginWithRedirect`.
|
|
1089
1120
|
// To resolve that, we add a second-level locking that locks only the iframe calls in
|
|
1090
1121
|
// the same way as was done before https://github.com/auth0/auth0-spa-js/pull/1408.
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1122
|
+
try {
|
|
1123
|
+
return await this.lockManager.runWithLock(
|
|
1124
|
+
iframeLockKey,
|
|
1125
|
+
5000,
|
|
1126
|
+
async () => {
|
|
1127
|
+
const params: AuthorizationParams & { scope: string } = {
|
|
1128
|
+
...options.authorizationParams,
|
|
1129
|
+
prompt: 'none'
|
|
1130
|
+
};
|
|
1099
1131
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1132
|
+
const orgHint = this.cookieStorage.get<string>(
|
|
1133
|
+
this.orgHintCookieName
|
|
1134
|
+
);
|
|
1103
1135
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
nonce: nonceIn,
|
|
1108
|
-
code_verifier,
|
|
1109
|
-
redirect_uri,
|
|
1110
|
-
scope,
|
|
1111
|
-
audience
|
|
1112
|
-
} = await this._prepareAuthorizeUrl(
|
|
1113
|
-
params,
|
|
1114
|
-
{ response_mode: 'web_message' },
|
|
1115
|
-
window.location.origin
|
|
1116
|
-
);
|
|
1136
|
+
if (orgHint && !params.organization) {
|
|
1137
|
+
params.organization = orgHint;
|
|
1138
|
+
}
|
|
1117
1139
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1140
|
+
const {
|
|
1141
|
+
url,
|
|
1142
|
+
state: stateIn,
|
|
1143
|
+
nonce: nonceIn,
|
|
1144
|
+
code_verifier,
|
|
1145
|
+
redirect_uri,
|
|
1146
|
+
scope,
|
|
1147
|
+
audience
|
|
1148
|
+
} = await this._prepareAuthorizeUrl(
|
|
1149
|
+
params,
|
|
1150
|
+
{ response_mode: 'web_message' },
|
|
1151
|
+
window.location.origin
|
|
1125
1152
|
);
|
|
1126
|
-
}
|
|
1127
1153
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1154
|
+
// When a browser is running in a Cross-Origin Isolated context, using iframes is not possible.
|
|
1155
|
+
// It doesn't throw an error but times out instead, so we should exit early and inform the user about the reason.
|
|
1156
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated
|
|
1157
|
+
if ((window as any).crossOriginIsolated) {
|
|
1158
|
+
throw new GenericError(
|
|
1159
|
+
'login_required',
|
|
1160
|
+
'The application is running in a Cross-Origin Isolated context, silently retrieving a token without refresh token is not possible.'
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1130
1163
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
try {
|
|
1134
|
-
eventOrigin = new URL(this.domainUrl).origin;
|
|
1135
|
-
} catch {
|
|
1136
|
-
eventOrigin = this.domainUrl;
|
|
1137
|
-
}
|
|
1164
|
+
const authorizeTimeout =
|
|
1165
|
+
options.timeoutInSeconds || this.options.authorizeTimeoutInSeconds;
|
|
1138
1166
|
|
|
1139
|
-
|
|
1167
|
+
// Extract origin from domainUrl, fallback to domainUrl if URL parsing fails
|
|
1168
|
+
let eventOrigin: string;
|
|
1169
|
+
try {
|
|
1170
|
+
eventOrigin = new URL(this.domainUrl).origin;
|
|
1171
|
+
} catch {
|
|
1172
|
+
eventOrigin = this.domainUrl;
|
|
1173
|
+
}
|
|
1140
1174
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1175
|
+
const codeResult = await runIframe(
|
|
1176
|
+
url,
|
|
1177
|
+
eventOrigin,
|
|
1178
|
+
authorizeTimeout
|
|
1179
|
+
);
|
|
1144
1180
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
...options.authorizationParams,
|
|
1148
|
-
code_verifier,
|
|
1149
|
-
code: codeResult.code as string,
|
|
1150
|
-
grant_type: 'authorization_code',
|
|
1151
|
-
redirect_uri,
|
|
1152
|
-
timeout: options.authorizationParams.timeout || this.httpTimeoutMs
|
|
1153
|
-
},
|
|
1154
|
-
{
|
|
1155
|
-
nonceIn,
|
|
1156
|
-
organization: params.organization
|
|
1181
|
+
if (stateIn !== codeResult.state) {
|
|
1182
|
+
throw new GenericError('state_mismatch', 'Invalid state');
|
|
1157
1183
|
}
|
|
1158
|
-
);
|
|
1159
1184
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1185
|
+
const tokenResult = await this._requestToken(
|
|
1186
|
+
{
|
|
1187
|
+
...options.authorizationParams,
|
|
1188
|
+
code_verifier,
|
|
1189
|
+
code: codeResult.code as string,
|
|
1190
|
+
grant_type: 'authorization_code',
|
|
1191
|
+
redirect_uri,
|
|
1192
|
+
timeout: options.authorizationParams.timeout || this.httpTimeoutMs
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
nonceIn,
|
|
1196
|
+
organization: params.organization
|
|
1197
|
+
}
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
return {
|
|
1201
|
+
...tokenResult,
|
|
1202
|
+
scope: scope,
|
|
1203
|
+
oauthTokenScope: tokenResult.scope,
|
|
1204
|
+
audience: audience
|
|
1205
|
+
};
|
|
1171
1206
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1207
|
+
);
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
if (e.error === 'login_required') {
|
|
1210
|
+
this.logout({
|
|
1211
|
+
openUrl: false
|
|
1212
|
+
});
|
|
1175
1213
|
}
|
|
1176
|
-
|
|
1177
|
-
throw new TimeoutError();
|
|
1214
|
+
throw e;
|
|
1178
1215
|
}
|
|
1179
1216
|
}
|
|
1180
1217
|
|
|
@@ -1413,23 +1450,6 @@ export class Auth0Client {
|
|
|
1413
1450
|
}
|
|
1414
1451
|
}
|
|
1415
1452
|
|
|
1416
|
-
/**
|
|
1417
|
-
* Releases any locks acquired by the current page that are not released yet
|
|
1418
|
-
*
|
|
1419
|
-
* Get's called on the `pagehide` event.
|
|
1420
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
|
|
1421
|
-
*/
|
|
1422
|
-
private _releaseLockOnPageHide = async () => {
|
|
1423
|
-
// Release all active locks
|
|
1424
|
-
const lockKeysToRelease = Array.from(this.activeLockKeys);
|
|
1425
|
-
for (const lockKey of lockKeysToRelease) {
|
|
1426
|
-
await lock.releaseLock(lockKey);
|
|
1427
|
-
}
|
|
1428
|
-
this.activeLockKeys.clear();
|
|
1429
|
-
|
|
1430
|
-
window.removeEventListener('pagehide', this._releaseLockOnPageHide);
|
|
1431
|
-
};
|
|
1432
|
-
|
|
1433
1453
|
private async _requestToken(
|
|
1434
1454
|
options:
|
|
1435
1455
|
| PKCERequestTokenOptions
|
|
@@ -270,11 +270,15 @@ export class CacheManager {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
|
-
* Updates in
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
|
|
273
|
+
* Updates the refresh token in all cache entries that contain the old refresh token.
|
|
274
|
+
*
|
|
275
|
+
* When a refresh token is rotated, multiple cache entries (for different audiences/scopes)
|
|
276
|
+
* may share the same refresh token. This method propagates the new refresh token to all
|
|
277
|
+
* matching entries.
|
|
278
|
+
*
|
|
279
|
+
* @param oldRefreshToken The refresh token that was used and is now invalid
|
|
280
|
+
* @param newRefreshToken The new refresh token received from the server
|
|
281
|
+
*/
|
|
278
282
|
async updateEntry(
|
|
279
283
|
oldRefreshToken: string,
|
|
280
284
|
newRefreshToken: string,
|
|
@@ -287,12 +291,8 @@ export class CacheManager {
|
|
|
287
291
|
const entry = await this.cache.get<WrappedCacheEntry>(key);
|
|
288
292
|
|
|
289
293
|
if (entry?.body?.refresh_token === oldRefreshToken) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
refresh_token: newRefreshToken,
|
|
293
|
-
} as CacheEntry;
|
|
294
|
-
|
|
295
|
-
await this.set(cacheEntry);
|
|
294
|
+
entry.body.refresh_token = newRefreshToken;
|
|
295
|
+
await this.cache.set(key, entry);
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
}
|
package/src/global.ts
CHANGED
|
@@ -2,6 +2,13 @@ import { ICache } from './cache';
|
|
|
2
2
|
import type { Dpop } from './dpop/dpop';
|
|
3
3
|
import { CompleteResponse } from './MyAccountApiClient';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Configuration option for automatic interactive error handling.
|
|
7
|
+
*
|
|
8
|
+
* - `'popup'`: SDK automatically opens Universal Login popup on MFA error
|
|
9
|
+
*/
|
|
10
|
+
export type InteractiveErrorHandler = 'popup';
|
|
11
|
+
|
|
5
12
|
export interface AuthorizationParams {
|
|
6
13
|
/**
|
|
7
14
|
* - `'page'`: displays the UI with a full page view
|
|
@@ -296,6 +303,21 @@ export interface Auth0ClientOptions {
|
|
|
296
303
|
*/
|
|
297
304
|
useDpop?: boolean;
|
|
298
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Configures automatic handling of interactive authentication errors.
|
|
308
|
+
*
|
|
309
|
+
* When set, the SDK intercepts `mfa_required` errors from `getTokenSilently()`
|
|
310
|
+
* and handles them automatically instead of throwing to the caller.
|
|
311
|
+
*
|
|
312
|
+
* - `'popup'`: Opens Universal Login in a popup to complete MFA.
|
|
313
|
+
* The original `authorizationParams` (audience, scope) are preserved.
|
|
314
|
+
* On success, the token is returned. On failure, popup errors are thrown.
|
|
315
|
+
*
|
|
316
|
+
* This option only affects `getTokenSilently()`. Other methods are not affected.
|
|
317
|
+
*
|
|
318
|
+
* @default undefined (MFA errors are thrown to the caller)
|
|
319
|
+
*/
|
|
320
|
+
interactiveErrorHandler?: InteractiveErrorHandler;
|
|
299
321
|
|
|
300
322
|
/**
|
|
301
323
|
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
|