@auth0/auth0-spa-js 2.4.1 → 2.6.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 +298 -43
- 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/auth0-spa-js.worker.development.js +34 -2
- package/dist/auth0-spa-js.worker.development.js.map +1 -1
- package/dist/auth0-spa-js.worker.production.js +1 -1
- package/dist/auth0-spa-js.worker.production.js.map +1 -1
- package/dist/lib/auth0-spa-js.cjs.js +309 -44
- package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
- package/dist/typings/Auth0Client.d.ts +42 -2
- package/dist/typings/Auth0Client.utils.d.ts +32 -0
- package/dist/typings/MyAccountApiClient.d.ts +92 -0
- package/dist/typings/api.d.ts +1 -1
- package/dist/typings/cache/cache-manager.d.ts +18 -1
- package/dist/typings/errors.d.ts +10 -0
- package/dist/typings/fetcher.d.ts +11 -7
- package/dist/typings/global.d.ts +97 -0
- package/dist/typings/http.d.ts +2 -2
- package/dist/typings/index.d.ts +2 -1
- package/dist/typings/transaction-manager.d.ts +15 -4
- package/dist/typings/version.d.ts +1 -1
- package/dist/typings/worker/worker.types.d.ts +1 -0
- package/package.json +1 -1
- package/src/Auth0Client.ts +282 -25
- package/src/Auth0Client.utils.ts +66 -0
- package/src/MyAccountApiClient.ts +158 -0
- package/src/api.ts +7 -1
- package/src/cache/cache-manager.ts +82 -7
- package/src/errors.ts +18 -0
- package/src/fetcher.ts +30 -18
- package/src/global.ts +112 -4
- package/src/http.ts +12 -5
- package/src/index.ts +5 -0
- package/src/transaction-manager.ts +17 -4
- package/src/utils.ts +1 -0
- package/src/version.ts +1 -1
- package/src/worker/token.worker.ts +60 -9
- package/src/worker/worker.types.ts +1 -0
package/src/Auth0Client.ts
CHANGED
|
@@ -31,10 +31,11 @@ import {
|
|
|
31
31
|
DecodedToken
|
|
32
32
|
} from './cache';
|
|
33
33
|
|
|
34
|
-
import { TransactionManager } from './transaction-manager';
|
|
34
|
+
import { ConnectAccountTransaction, LoginTransaction, TransactionManager } from './transaction-manager';
|
|
35
35
|
import { verify as verifyIdToken } from './jwt';
|
|
36
36
|
import {
|
|
37
37
|
AuthenticationError,
|
|
38
|
+
ConnectError,
|
|
38
39
|
GenericError,
|
|
39
40
|
MissingRefreshTokenError,
|
|
40
41
|
TimeoutError
|
|
@@ -76,7 +77,11 @@ import {
|
|
|
76
77
|
User,
|
|
77
78
|
IdToken,
|
|
78
79
|
GetTokenSilentlyVerboseResponse,
|
|
79
|
-
TokenEndpointResponse
|
|
80
|
+
TokenEndpointResponse,
|
|
81
|
+
AuthenticationResult,
|
|
82
|
+
ConnectAccountRedirectResult,
|
|
83
|
+
RedirectConnectAccountOptions,
|
|
84
|
+
ResponseType
|
|
80
85
|
} from './global';
|
|
81
86
|
|
|
82
87
|
// @ts-ignore
|
|
@@ -90,7 +95,10 @@ import {
|
|
|
90
95
|
getAuthorizeParams,
|
|
91
96
|
GET_TOKEN_SILENTLY_LOCK_KEY,
|
|
92
97
|
OLD_IS_AUTHENTICATED_COOKIE_NAME,
|
|
93
|
-
patchOpenUrlWithOnRedirect
|
|
98
|
+
patchOpenUrlWithOnRedirect,
|
|
99
|
+
getScopeToRequest,
|
|
100
|
+
allScopesAreIncluded,
|
|
101
|
+
isRefreshWithMrrt
|
|
94
102
|
} from './Auth0Client.utils';
|
|
95
103
|
import { CustomTokenExchangeOptions } from './TokenExchange';
|
|
96
104
|
import { Dpop } from './dpop/dpop';
|
|
@@ -99,6 +107,7 @@ import {
|
|
|
99
107
|
type FetcherConfig,
|
|
100
108
|
type CustomFetchMinimalOutput
|
|
101
109
|
} from './fetcher';
|
|
110
|
+
import { MyAccountApiClient } from './MyAccountApiClient';
|
|
102
111
|
|
|
103
112
|
/**
|
|
104
113
|
* @ignore
|
|
@@ -135,6 +144,7 @@ export class Auth0Client {
|
|
|
135
144
|
authorizationParams: AuthorizationParams;
|
|
136
145
|
};
|
|
137
146
|
private readonly userCache: ICache = new InMemoryCache().enclosedCache;
|
|
147
|
+
private readonly myAccountApi: MyAccountApiClient;
|
|
138
148
|
|
|
139
149
|
private worker?: Worker;
|
|
140
150
|
private readonly defaultOptions: Partial<Auth0ClientOptions> = {
|
|
@@ -235,6 +245,22 @@ export class Auth0Client {
|
|
|
235
245
|
this.domainUrl = getDomain(this.options.domain);
|
|
236
246
|
this.tokenIssuer = getTokenIssuer(this.options.issuer, this.domainUrl);
|
|
237
247
|
|
|
248
|
+
const myAccountApiIdentifier = `${this.domainUrl}/me/`;
|
|
249
|
+
const myAccountFetcher = this.createFetcher({
|
|
250
|
+
...(this.options.useDpop && { dpopNonceId: '__auth0_my_account_api__' }),
|
|
251
|
+
getAccessToken: () =>
|
|
252
|
+
this.getTokenSilently({
|
|
253
|
+
authorizationParams: {
|
|
254
|
+
scope: 'create:me:connected_accounts',
|
|
255
|
+
audience: myAccountApiIdentifier
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
this.myAccountApi = new MyAccountApiClient(
|
|
260
|
+
myAccountFetcher,
|
|
261
|
+
myAccountApiIdentifier
|
|
262
|
+
);
|
|
263
|
+
|
|
238
264
|
// Don't use web workers unless using refresh tokens in memory
|
|
239
265
|
if (
|
|
240
266
|
typeof window !== 'undefined' &&
|
|
@@ -321,8 +347,8 @@ export class Auth0Client {
|
|
|
321
347
|
nonce,
|
|
322
348
|
code_challenge,
|
|
323
349
|
authorizationParams.redirect_uri ||
|
|
324
|
-
|
|
325
|
-
|
|
350
|
+
this.options.authorizationParams.redirect_uri ||
|
|
351
|
+
fallbackRedirectUri,
|
|
326
352
|
authorizeOptions?.response_mode,
|
|
327
353
|
thumbprint
|
|
328
354
|
);
|
|
@@ -474,9 +500,10 @@ export class Auth0Client {
|
|
|
474
500
|
urlOptions.authorizationParams || {}
|
|
475
501
|
);
|
|
476
502
|
|
|
477
|
-
this.transactionManager.create({
|
|
503
|
+
this.transactionManager.create<LoginTransaction>({
|
|
478
504
|
...transaction,
|
|
479
505
|
appState,
|
|
506
|
+
response_type: ResponseType.Code,
|
|
480
507
|
...(organization && { organization })
|
|
481
508
|
});
|
|
482
509
|
|
|
@@ -497,18 +524,18 @@ export class Auth0Client {
|
|
|
497
524
|
*/
|
|
498
525
|
public async handleRedirectCallback<TAppState = any>(
|
|
499
526
|
url: string = window.location.href
|
|
500
|
-
): Promise<
|
|
527
|
+
): Promise<
|
|
528
|
+
RedirectLoginResult<TAppState> | ConnectAccountRedirectResult<TAppState>
|
|
529
|
+
> {
|
|
501
530
|
const queryStringFragments = url.split('?').slice(1);
|
|
502
531
|
|
|
503
532
|
if (queryStringFragments.length === 0) {
|
|
504
533
|
throw new Error('There are no query params available for parsing.');
|
|
505
534
|
}
|
|
506
535
|
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
const transaction = this.transactionManager.get();
|
|
536
|
+
const transaction = this.transactionManager.get<
|
|
537
|
+
LoginTransaction | ConnectAccountTransaction
|
|
538
|
+
>();
|
|
512
539
|
|
|
513
540
|
if (!transaction) {
|
|
514
541
|
throw new GenericError('missing_transaction', 'Invalid state');
|
|
@@ -516,6 +543,38 @@ export class Auth0Client {
|
|
|
516
543
|
|
|
517
544
|
this.transactionManager.remove();
|
|
518
545
|
|
|
546
|
+
const authenticationResult = parseAuthenticationResult(
|
|
547
|
+
queryStringFragments.join('')
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
if (transaction.response_type === ResponseType.ConnectCode) {
|
|
551
|
+
return this._handleConnectAccountRedirectCallback<TAppState>(
|
|
552
|
+
authenticationResult,
|
|
553
|
+
transaction
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return this._handleLoginRedirectCallback<TAppState>(
|
|
557
|
+
authenticationResult,
|
|
558
|
+
transaction
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Handles the redirect callback from the login flow.
|
|
564
|
+
*
|
|
565
|
+
* @template AppState - The application state persisted from the /authorize redirect.
|
|
566
|
+
* @param {string} authenticationResult - The parsed authentication result from the URL.
|
|
567
|
+
* @param {string} transaction - The login transaction.
|
|
568
|
+
*
|
|
569
|
+
* @returns {RedirectLoginResult} Resolves with the persisted app state.
|
|
570
|
+
* @throws {GenericError | Error} If the transaction is missing, invalid, or the code exchange fails.
|
|
571
|
+
*/
|
|
572
|
+
private async _handleLoginRedirectCallback<TAppState>(
|
|
573
|
+
authenticationResult: AuthenticationResult,
|
|
574
|
+
transaction: LoginTransaction
|
|
575
|
+
): Promise<RedirectLoginResult<TAppState>> {
|
|
576
|
+
const { code, state, error, error_description } = authenticationResult;
|
|
577
|
+
|
|
519
578
|
if (error) {
|
|
520
579
|
throw new AuthenticationError(
|
|
521
580
|
error,
|
|
@@ -550,7 +609,63 @@ export class Auth0Client {
|
|
|
550
609
|
);
|
|
551
610
|
|
|
552
611
|
return {
|
|
553
|
-
appState: transaction.appState
|
|
612
|
+
appState: transaction.appState,
|
|
613
|
+
response_type: ResponseType.Code
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Handles the redirect callback from the connect account flow.
|
|
619
|
+
* This works the same as the redirect from the login flow expect it verifies the `connect_code`
|
|
620
|
+
* with the My Account API rather than the `code` with the Authorization Server.
|
|
621
|
+
*
|
|
622
|
+
* @template AppState - The application state persisted from the connect redirect.
|
|
623
|
+
* @param {string} connectResult - The parsed connect accounts result from the URL.
|
|
624
|
+
* @param {string} transaction - The login transaction.
|
|
625
|
+
* @returns {Promise<ConnectAccountRedirectResult>} The result of the My Account API, including any persisted app state.
|
|
626
|
+
* @throws {GenericError | MyAccountApiError} If the transaction is missing, invalid, or an error is returned from the My Account API.
|
|
627
|
+
*/
|
|
628
|
+
private async _handleConnectAccountRedirectCallback<TAppState>(
|
|
629
|
+
connectResult: AuthenticationResult,
|
|
630
|
+
transaction: ConnectAccountTransaction
|
|
631
|
+
): Promise<ConnectAccountRedirectResult<TAppState>> {
|
|
632
|
+
const { connect_code, state, error, error_description } = connectResult;
|
|
633
|
+
|
|
634
|
+
if (error) {
|
|
635
|
+
throw new ConnectError(
|
|
636
|
+
error,
|
|
637
|
+
error_description || error,
|
|
638
|
+
transaction.connection,
|
|
639
|
+
state,
|
|
640
|
+
transaction.appState
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (!connect_code) {
|
|
645
|
+
throw new GenericError('missing_connect_code', 'Missing connect code');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (
|
|
649
|
+
!transaction.code_verifier ||
|
|
650
|
+
!transaction.state ||
|
|
651
|
+
!transaction.auth_session ||
|
|
652
|
+
!transaction.redirect_uri ||
|
|
653
|
+
transaction.state !== state
|
|
654
|
+
) {
|
|
655
|
+
throw new GenericError('state_mismatch', 'Invalid state');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const data = await this.myAccountApi.completeAccount({
|
|
659
|
+
auth_session: transaction.auth_session,
|
|
660
|
+
connect_code,
|
|
661
|
+
redirect_uri: transaction.redirect_uri,
|
|
662
|
+
code_verifier: transaction.code_verifier
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
...data,
|
|
667
|
+
appState: transaction.appState,
|
|
668
|
+
response_type: ResponseType.ConnectCode,
|
|
554
669
|
};
|
|
555
670
|
}
|
|
556
671
|
|
|
@@ -596,7 +711,7 @@ export class Auth0Client {
|
|
|
596
711
|
|
|
597
712
|
try {
|
|
598
713
|
await this.getTokenSilently(options);
|
|
599
|
-
} catch (_) {}
|
|
714
|
+
} catch (_) { }
|
|
600
715
|
}
|
|
601
716
|
|
|
602
717
|
/**
|
|
@@ -689,7 +804,8 @@ export class Auth0Client {
|
|
|
689
804
|
const entry = await this._getEntryFromCache({
|
|
690
805
|
scope: getTokenOptions.authorizationParams.scope,
|
|
691
806
|
audience: getTokenOptions.authorizationParams.audience || 'default',
|
|
692
|
-
clientId: this.options.clientId
|
|
807
|
+
clientId: this.options.clientId,
|
|
808
|
+
cacheMode,
|
|
693
809
|
});
|
|
694
810
|
|
|
695
811
|
if (entry) {
|
|
@@ -789,7 +905,9 @@ export class Auth0Client {
|
|
|
789
905
|
scope: localOptions.authorizationParams.scope,
|
|
790
906
|
audience: localOptions.authorizationParams.audience || 'default',
|
|
791
907
|
clientId: this.options.clientId
|
|
792
|
-
})
|
|
908
|
+
}),
|
|
909
|
+
undefined,
|
|
910
|
+
this.options.useMrrt
|
|
793
911
|
);
|
|
794
912
|
|
|
795
913
|
return cache!.access_token;
|
|
@@ -976,7 +1094,9 @@ export class Auth0Client {
|
|
|
976
1094
|
scope: options.authorizationParams.scope,
|
|
977
1095
|
audience: options.authorizationParams.audience || 'default',
|
|
978
1096
|
clientId: this.options.clientId
|
|
979
|
-
})
|
|
1097
|
+
}),
|
|
1098
|
+
undefined,
|
|
1099
|
+
this.options.useMrrt
|
|
980
1100
|
);
|
|
981
1101
|
|
|
982
1102
|
// If you don't have a refresh token in memory
|
|
@@ -1004,6 +1124,13 @@ export class Auth0Client {
|
|
|
1004
1124
|
? options.timeoutInSeconds * 1000
|
|
1005
1125
|
: null;
|
|
1006
1126
|
|
|
1127
|
+
const scopesToRequest = getScopeToRequest(
|
|
1128
|
+
this.options.useMrrt,
|
|
1129
|
+
options.authorizationParams,
|
|
1130
|
+
cache?.audience,
|
|
1131
|
+
cache?.scope,
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1007
1134
|
try {
|
|
1008
1135
|
const tokenResult = await this._requestToken({
|
|
1009
1136
|
...options.authorizationParams,
|
|
@@ -1011,7 +1138,51 @@ export class Auth0Client {
|
|
|
1011
1138
|
refresh_token: cache && cache.refresh_token,
|
|
1012
1139
|
redirect_uri,
|
|
1013
1140
|
...(timeout && { timeout })
|
|
1014
|
-
}
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
scopesToRequest,
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
// If is refreshed with MRRT, we update all entries that have the old
|
|
1148
|
+
// refresh_token with the new one if the server responded with one
|
|
1149
|
+
if (tokenResult.refresh_token && this.options.useMrrt && cache?.refresh_token) {
|
|
1150
|
+
await this.cacheManager.updateEntry(
|
|
1151
|
+
cache.refresh_token,
|
|
1152
|
+
tokenResult.refresh_token
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Some scopes requested to the server might not be inside the refresh policies
|
|
1157
|
+
// In order to return a token with all requested scopes when using MRRT we should
|
|
1158
|
+
// check if all scopes are returned. If not, we will try to use an iframe to request
|
|
1159
|
+
// a token.
|
|
1160
|
+
if (this.options.useMrrt) {
|
|
1161
|
+
const isRefreshMrrt = isRefreshWithMrrt(
|
|
1162
|
+
cache?.audience,
|
|
1163
|
+
cache?.scope,
|
|
1164
|
+
options.authorizationParams.audience,
|
|
1165
|
+
options.authorizationParams.scope,
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
if (isRefreshMrrt) {
|
|
1169
|
+
const tokenHasAllScopes = allScopesAreIncluded(
|
|
1170
|
+
scopesToRequest,
|
|
1171
|
+
tokenResult.scope,
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
if (!tokenHasAllScopes) {
|
|
1175
|
+
if (this.options.useRefreshTokensFallback) {
|
|
1176
|
+
return await this._getTokenFromIFrame(options);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
throw new MissingRefreshTokenError(
|
|
1180
|
+
options.authorizationParams.audience || 'default',
|
|
1181
|
+
options.authorizationParams.scope,
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1015
1186
|
|
|
1016
1187
|
return {
|
|
1017
1188
|
...tokenResult,
|
|
@@ -1084,11 +1255,13 @@ export class Auth0Client {
|
|
|
1084
1255
|
private async _getEntryFromCache({
|
|
1085
1256
|
scope,
|
|
1086
1257
|
audience,
|
|
1087
|
-
clientId
|
|
1258
|
+
clientId,
|
|
1259
|
+
cacheMode,
|
|
1088
1260
|
}: {
|
|
1089
1261
|
scope: string;
|
|
1090
1262
|
audience: string;
|
|
1091
1263
|
clientId: string;
|
|
1264
|
+
cacheMode?: string;
|
|
1092
1265
|
}): Promise<undefined | GetTokenSilentlyVerboseResponse> {
|
|
1093
1266
|
const entry = await this.cacheManager.get(
|
|
1094
1267
|
new CacheKey({
|
|
@@ -1096,7 +1269,9 @@ export class Auth0Client {
|
|
|
1096
1269
|
audience,
|
|
1097
1270
|
clientId
|
|
1098
1271
|
}),
|
|
1099
|
-
60 // get a new token if within 60 seconds of expiring
|
|
1272
|
+
60, // get a new token if within 60 seconds of expiring
|
|
1273
|
+
this.options.useMrrt,
|
|
1274
|
+
cacheMode,
|
|
1100
1275
|
);
|
|
1101
1276
|
|
|
1102
1277
|
if (entry && entry.access_token) {
|
|
@@ -1134,7 +1309,7 @@ export class Auth0Client {
|
|
|
1134
1309
|
| TokenExchangeRequestOptions,
|
|
1135
1310
|
additionalParameters?: RequestTokenAdditionalParameters
|
|
1136
1311
|
) {
|
|
1137
|
-
const { nonceIn, organization } = additionalParameters || {};
|
|
1312
|
+
const { nonceIn, organization, scopesToRequest } = additionalParameters || {};
|
|
1138
1313
|
const authResult = await oauthToken(
|
|
1139
1314
|
{
|
|
1140
1315
|
baseUrl: this.domainUrl,
|
|
@@ -1142,8 +1317,10 @@ export class Auth0Client {
|
|
|
1142
1317
|
auth0Client: this.options.auth0Client,
|
|
1143
1318
|
useFormData: this.options.useFormData,
|
|
1144
1319
|
timeout: this.httpTimeoutMs,
|
|
1320
|
+
useMrrt: this.options.useMrrt,
|
|
1145
1321
|
dpop: this.dpop,
|
|
1146
|
-
...options
|
|
1322
|
+
...options,
|
|
1323
|
+
scope: scopesToRequest || options.scope,
|
|
1147
1324
|
},
|
|
1148
1325
|
this.worker
|
|
1149
1326
|
);
|
|
@@ -1298,7 +1475,7 @@ export class Auth0Client {
|
|
|
1298
1475
|
* This is a drop-in replacement for the Fetch API's `fetch()` method, but will
|
|
1299
1476
|
* handle certain authentication logic for you, like building the proper auth
|
|
1300
1477
|
* headers or managing DPoP nonces and retries automatically.
|
|
1301
|
-
*
|
|
1478
|
+
*
|
|
1302
1479
|
* Check the `EXAMPLES.md` file for a deeper look into this method.
|
|
1303
1480
|
*/
|
|
1304
1481
|
public createFetcher<TOutput extends CustomFetchMinimalOutput = Response>(
|
|
@@ -1312,12 +1489,91 @@ export class Auth0Client {
|
|
|
1312
1489
|
|
|
1313
1490
|
return new Fetcher(config, {
|
|
1314
1491
|
isDpopEnabled: () => !!this.options.useDpop,
|
|
1315
|
-
getAccessToken:
|
|
1492
|
+
getAccessToken: authParams =>
|
|
1493
|
+
this.getTokenSilently({
|
|
1494
|
+
authorizationParams: {
|
|
1495
|
+
scope: authParams?.scope?.join(' '),
|
|
1496
|
+
audience: authParams?.audience
|
|
1497
|
+
}
|
|
1498
|
+
}),
|
|
1316
1499
|
getDpopNonce: () => this.getDpopNonce(config.dpopNonceId),
|
|
1317
|
-
setDpopNonce: nonce => this.setDpopNonce(nonce),
|
|
1500
|
+
setDpopNonce: nonce => this.setDpopNonce(nonce, config.dpopNonceId),
|
|
1318
1501
|
generateDpopProof: params => this.generateDpopProof(params)
|
|
1319
1502
|
});
|
|
1320
1503
|
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Initiates a redirect to connect the user's account with a specified connection.
|
|
1507
|
+
* This method generates PKCE parameters, creates a transaction, and redirects to the /connect endpoint.
|
|
1508
|
+
*
|
|
1509
|
+
* @template TAppState - The application state to persist through the transaction.
|
|
1510
|
+
* @param {RedirectConnectAccountOptions<TAppState>} options - Options for the connect account redirect flow.
|
|
1511
|
+
* @param {string} options.connection - The name of the connection to link (e.g. 'google-oauth2').
|
|
1512
|
+
* @param {AuthorizationParams} [options.authorization_params] - Additional authorization parameters for the request to the upstream IdP.
|
|
1513
|
+
* @param {string} [options.redirectUri] - The URI to redirect back to after connecting the account.
|
|
1514
|
+
* @param {TAppState} [options.appState] - Application state to persist through the transaction.
|
|
1515
|
+
* @param {(url: string) => Promise<void>} [options.openUrl] - Custom function to open the URL.
|
|
1516
|
+
*
|
|
1517
|
+
* @returns {Promise<void>} Resolves when the redirect is initiated.
|
|
1518
|
+
* @throws {MyAccountApiError} If the connect request to the My Account API fails.
|
|
1519
|
+
*/
|
|
1520
|
+
public async connectAccountWithRedirect<TAppState = any>(
|
|
1521
|
+
options: RedirectConnectAccountOptions<TAppState>
|
|
1522
|
+
) {
|
|
1523
|
+
if (!this.options.useDpop) {
|
|
1524
|
+
throw new Error('`useDpop` option must be enabled before using connectAccountWithRedirect.');
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (!this.options.useMrrt) {
|
|
1528
|
+
throw new Error('`useMrrt` option must be enabled before using connectAccountWithRedirect.');
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
const {
|
|
1532
|
+
openUrl,
|
|
1533
|
+
appState,
|
|
1534
|
+
connection,
|
|
1535
|
+
authorization_params,
|
|
1536
|
+
redirectUri = this.options.authorizationParams.redirect_uri ||
|
|
1537
|
+
window.location.origin
|
|
1538
|
+
} = options;
|
|
1539
|
+
|
|
1540
|
+
if (!connection) {
|
|
1541
|
+
throw new Error('connection is required');
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const state = encode(createRandomString());
|
|
1545
|
+
const code_verifier = createRandomString();
|
|
1546
|
+
const code_challengeBuffer = await sha256(code_verifier);
|
|
1547
|
+
const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
|
|
1548
|
+
|
|
1549
|
+
const { connect_uri, connect_params, auth_session } =
|
|
1550
|
+
await this.myAccountApi.connectAccount({
|
|
1551
|
+
connection,
|
|
1552
|
+
redirect_uri: redirectUri,
|
|
1553
|
+
state,
|
|
1554
|
+
code_challenge,
|
|
1555
|
+
code_challenge_method: 'S256',
|
|
1556
|
+
authorization_params
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
this.transactionManager.create<ConnectAccountTransaction>({
|
|
1560
|
+
state,
|
|
1561
|
+
code_verifier,
|
|
1562
|
+
auth_session,
|
|
1563
|
+
redirect_uri: redirectUri,
|
|
1564
|
+
appState,
|
|
1565
|
+
connection,
|
|
1566
|
+
response_type: ResponseType.ConnectCode
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
const url = new URL(connect_uri);
|
|
1570
|
+
url.searchParams.set('ticket', connect_params.ticket);
|
|
1571
|
+
if (openUrl) {
|
|
1572
|
+
await openUrl(url.toString());
|
|
1573
|
+
} else {
|
|
1574
|
+
window.location.assign(url);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1321
1577
|
}
|
|
1322
1578
|
|
|
1323
1579
|
interface BaseRequestTokenOptions {
|
|
@@ -1349,4 +1605,5 @@ interface TokenExchangeRequestOptions extends BaseRequestTokenOptions {
|
|
|
1349
1605
|
interface RequestTokenAdditionalParameters {
|
|
1350
1606
|
nonceIn?: string;
|
|
1351
1607
|
organization?: string;
|
|
1608
|
+
scopesToRequest?: string;
|
|
1352
1609
|
}
|
package/src/Auth0Client.utils.ts
CHANGED
|
@@ -96,3 +96,69 @@ export const patchOpenUrlWithOnRedirect = <
|
|
|
96
96
|
|
|
97
97
|
return result as T;
|
|
98
98
|
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @ignore
|
|
102
|
+
*
|
|
103
|
+
* Checks if all scopes are included inside other array of scopes
|
|
104
|
+
*/
|
|
105
|
+
export const allScopesAreIncluded = (scopeToInclude?: string, scopes?: string): boolean => {
|
|
106
|
+
const scopeGroup = scopes?.split(" ") || [];
|
|
107
|
+
const scopesToInclude = scopeToInclude?.split(" ") || [];
|
|
108
|
+
return scopesToInclude.every((key) => scopeGroup.includes(key));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @ignore
|
|
113
|
+
*
|
|
114
|
+
* For backward compatibility we are going to check if we are going to downscope while doing a refresh request
|
|
115
|
+
* while MRRT is allowed. If the audience is the same for the refresh_token we are going to use and it has
|
|
116
|
+
* lower scopes than the ones originally in the token, we are going to return the scopes that were stored
|
|
117
|
+
* with the refresh_token in the tokenset.
|
|
118
|
+
* @param useMrrt Setting that the user can activate to use MRRT in their requests
|
|
119
|
+
* @param authorizationParams Contains the audience and scope that the user requested to obtain a token
|
|
120
|
+
* @param cachedAudience Audience stored with the refresh_token wich we are going to use in the request
|
|
121
|
+
* @param cachedScope Scope stored with the refresh_token wich we are going to use in the request
|
|
122
|
+
*/
|
|
123
|
+
export const getScopeToRequest = (
|
|
124
|
+
useMrrt: boolean | undefined,
|
|
125
|
+
authorizationParams: { audience?: string, scope: string },
|
|
126
|
+
cachedAudience?: string,
|
|
127
|
+
cachedScope?: string
|
|
128
|
+
): string => {
|
|
129
|
+
if (useMrrt && cachedAudience && cachedScope) {
|
|
130
|
+
if (authorizationParams.audience !== cachedAudience) {
|
|
131
|
+
return authorizationParams.scope;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const cachedScopes = cachedScope.split(" ");
|
|
135
|
+
const newScopes = authorizationParams.scope?.split(" ") || [];
|
|
136
|
+
const newScopesAreIncluded = newScopes.every((scope) => cachedScopes.includes(scope));
|
|
137
|
+
|
|
138
|
+
return cachedScopes.length >= newScopes.length && newScopesAreIncluded ? cachedScope : authorizationParams.scope;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return authorizationParams.scope;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @ignore
|
|
146
|
+
*
|
|
147
|
+
* Checks if the refresh request has been done using MRRT
|
|
148
|
+
* @param cachedAudience Audience from the refresh token used to refresh
|
|
149
|
+
* @param cachedScope Scopes from the refresh token used to refresh
|
|
150
|
+
* @param requestAudience Audience sent to the server
|
|
151
|
+
* @param requestScope Scopes sent to the server
|
|
152
|
+
*/
|
|
153
|
+
export const isRefreshWithMrrt = (
|
|
154
|
+
cachedAudience: string | undefined,
|
|
155
|
+
cachedScope: string | undefined,
|
|
156
|
+
requestAudience: string | undefined,
|
|
157
|
+
requestScope: string,
|
|
158
|
+
): boolean => {
|
|
159
|
+
if (cachedAudience !== requestAudience) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return !allScopesAreIncluded(requestScope, cachedScope);
|
|
164
|
+
}
|