@auth0/auth0-server-js 1.12.1 → 1.14.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/index.cjs +454 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -27
- package/dist/index.d.ts +150 -27
- package/dist/index.js +426 -194
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -168,6 +168,8 @@ function updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointR
|
|
|
168
168
|
import {
|
|
169
169
|
TokenForConnectionError,
|
|
170
170
|
AuthClient,
|
|
171
|
+
isFederatedDomain,
|
|
172
|
+
MissingCapturedResponseError,
|
|
171
173
|
OrganizationValidationError,
|
|
172
174
|
PasswordlessStartError,
|
|
173
175
|
PasswordlessVerifyError,
|
|
@@ -211,7 +213,7 @@ function getTelemetryConfig(config) {
|
|
|
211
213
|
return {
|
|
212
214
|
enabled: true,
|
|
213
215
|
name: config?.name ?? "@auth0/auth0-server-js",
|
|
214
|
-
version: config?.version ?? "1.
|
|
216
|
+
version: config?.version ?? "1.14.0"
|
|
215
217
|
};
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -231,8 +233,8 @@ var ServerMfaClient = class {
|
|
|
231
233
|
* @returns Promise resolving to an array of enrolled authenticators
|
|
232
234
|
* @throws {MfaListAuthenticatorsError} When the request fails
|
|
233
235
|
*/
|
|
234
|
-
async listAuthenticators(options) {
|
|
235
|
-
return this.#options.authClient.mfa.listAuthenticators(options);
|
|
236
|
+
async listAuthenticators(options, requestOptions) {
|
|
237
|
+
return this.#options.authClient.mfa.listAuthenticators(options, requestOptions);
|
|
236
238
|
}
|
|
237
239
|
/**
|
|
238
240
|
* Enrolls a new MFA authenticator for the user.
|
|
@@ -241,8 +243,8 @@ var ServerMfaClient = class {
|
|
|
241
243
|
* @returns Promise resolving to enrollment response with authenticator details
|
|
242
244
|
* @throws {MfaEnrollmentError} When enrollment fails
|
|
243
245
|
*/
|
|
244
|
-
async enrollAuthenticator(options) {
|
|
245
|
-
return this.#options.authClient.mfa.enrollAuthenticator(options);
|
|
246
|
+
async enrollAuthenticator(options, requestOptions) {
|
|
247
|
+
return this.#options.authClient.mfa.enrollAuthenticator(options, requestOptions);
|
|
246
248
|
}
|
|
247
249
|
/**
|
|
248
250
|
* Initiates an MFA challenge for user verification.
|
|
@@ -251,8 +253,8 @@ var ServerMfaClient = class {
|
|
|
251
253
|
* @returns Promise resolving to challenge response with challenge details
|
|
252
254
|
* @throws {MfaChallengeError} When the challenge fails
|
|
253
255
|
*/
|
|
254
|
-
async challengeAuthenticator(options) {
|
|
255
|
-
return this.#options.authClient.mfa.challengeAuthenticator(options);
|
|
256
|
+
async challengeAuthenticator(options, requestOptions) {
|
|
257
|
+
return this.#options.authClient.mfa.challengeAuthenticator(options, requestOptions);
|
|
256
258
|
}
|
|
257
259
|
/**
|
|
258
260
|
* Verifies an MFA challenge and completes the authentication flow.
|
|
@@ -266,8 +268,8 @@ var ServerMfaClient = class {
|
|
|
266
268
|
* @returns The tokens returned by Auth0 after successful verification
|
|
267
269
|
* @throws {MfaVerifyError} When verification fails (e.g. invalid token, wrong code)
|
|
268
270
|
*/
|
|
269
|
-
async verify(options, storeOptions) {
|
|
270
|
-
const tokenResponse = await this.#options.authClient.mfa.verify(options);
|
|
271
|
+
async verify(options, storeOptions, requestOptions) {
|
|
272
|
+
const tokenResponse = await this.#options.authClient.mfa.verify(options, requestOptions);
|
|
271
273
|
const audience = options.audience ?? this.#options.defaultAudience;
|
|
272
274
|
const existingStateData = await this.#options.stateStore.get(
|
|
273
275
|
this.#options.stateStoreIdentifier,
|
|
@@ -330,10 +332,10 @@ var ServerPasskeyClient = class {
|
|
|
330
332
|
*
|
|
331
333
|
* @returns A promise resolving to the signup challenge.
|
|
332
334
|
*/
|
|
333
|
-
async register(options, storeOptions) {
|
|
335
|
+
async register(options, storeOptions, requestOptions) {
|
|
334
336
|
const domain = await this.#options.resolveDomain(storeOptions);
|
|
335
337
|
const authClient = this.#options.getAuthClient(domain);
|
|
336
|
-
return authClient.passkey.register(options);
|
|
338
|
+
return authClient.passkey.register(options, requestOptions);
|
|
337
339
|
}
|
|
338
340
|
/**
|
|
339
341
|
* Requests a passkey login challenge for an existing user.
|
|
@@ -355,10 +357,10 @@ var ServerPasskeyClient = class {
|
|
|
355
357
|
*
|
|
356
358
|
* @returns A promise resolving to the login challenge.
|
|
357
359
|
*/
|
|
358
|
-
async challenge(options, storeOptions) {
|
|
360
|
+
async challenge(options, storeOptions, requestOptions) {
|
|
359
361
|
const domain = await this.#options.resolveDomain(storeOptions);
|
|
360
362
|
const authClient = this.#options.getAuthClient(domain);
|
|
361
|
-
return authClient.passkey.challenge(options);
|
|
363
|
+
return authClient.passkey.challenge(options, requestOptions);
|
|
362
364
|
}
|
|
363
365
|
/**
|
|
364
366
|
* Completes a passkey authentication flow (signup or login) by exchanging the
|
|
@@ -380,7 +382,7 @@ var ServerPasskeyClient = class {
|
|
|
380
382
|
*
|
|
381
383
|
* @returns A promise resolving to an object containing the authorizationDetails (when RAR was used).
|
|
382
384
|
*/
|
|
383
|
-
async getToken(options, storeOptions) {
|
|
385
|
+
async getToken(options, storeOptions, requestOptions) {
|
|
384
386
|
const scope = ensureOpenIdScope(options.scope ?? this.#options.defaultScope);
|
|
385
387
|
const audience = options.audience ?? this.#options.defaultAudience;
|
|
386
388
|
const domain = await this.#options.resolveDomain(storeOptions);
|
|
@@ -389,7 +391,7 @@ var ServerPasskeyClient = class {
|
|
|
389
391
|
...options,
|
|
390
392
|
scope,
|
|
391
393
|
audience
|
|
392
|
-
});
|
|
394
|
+
}, requestOptions);
|
|
393
395
|
const existingStateData = await this.#options.stateStore.get(this.#options.stateStoreIdentifier, storeOptions);
|
|
394
396
|
const stateData = updateStateData(audience ?? "default", existingStateData, tokenEndpointResponse, { domain });
|
|
395
397
|
await this.#options.stateStore.set(this.#options.stateStoreIdentifier, stateData, true, storeOptions);
|
|
@@ -422,9 +424,9 @@ var ServerDatabaseClient = class {
|
|
|
422
424
|
*
|
|
423
425
|
* @returns A promise resolving to the created user result with a normalized `id` field.
|
|
424
426
|
*/
|
|
425
|
-
async signUp(options, storeOptions) {
|
|
427
|
+
async signUp(options, storeOptions, requestOptions) {
|
|
426
428
|
const domain = await this.#options.resolveDomain(storeOptions);
|
|
427
|
-
return this.#options.getAuthClient(domain).database.signUp(options);
|
|
429
|
+
return this.#options.getAuthClient(domain).database.signUp(options, requestOptions);
|
|
428
430
|
}
|
|
429
431
|
/**
|
|
430
432
|
* Requests a password-change email for a database connection user.
|
|
@@ -440,12 +442,53 @@ var ServerDatabaseClient = class {
|
|
|
440
442
|
*
|
|
441
443
|
* @returns A promise resolving to the server's plain-text confirmation message.
|
|
442
444
|
*/
|
|
443
|
-
async changePassword(options, storeOptions) {
|
|
445
|
+
async changePassword(options, storeOptions, requestOptions) {
|
|
444
446
|
const domain = await this.#options.resolveDomain(storeOptions);
|
|
445
|
-
return this.#options.getAuthClient(domain).database.changePassword(options);
|
|
447
|
+
return this.#options.getAuthClient(domain).database.changePassword(options, requestOptions);
|
|
446
448
|
}
|
|
447
449
|
};
|
|
448
450
|
|
|
451
|
+
// src/enterprise-connect.ts
|
|
452
|
+
import { EnterpriseConnectNotSupportedError } from "@auth0/auth0-auth-js";
|
|
453
|
+
var EC_ALLOWED_METHODS = /* @__PURE__ */ new Set([
|
|
454
|
+
"startInteractiveLogin",
|
|
455
|
+
"startEnterpriseLogin",
|
|
456
|
+
"completeInteractiveLogin",
|
|
457
|
+
"logout",
|
|
458
|
+
"customTokenExchange",
|
|
459
|
+
"handleBackchannelLogout"
|
|
460
|
+
]);
|
|
461
|
+
var EC_ALLOWED_GETTERS = /* @__PURE__ */ new Set(["authClient"]);
|
|
462
|
+
var NullStateStore = class {
|
|
463
|
+
async get() {
|
|
464
|
+
return void 0;
|
|
465
|
+
}
|
|
466
|
+
async set() {
|
|
467
|
+
}
|
|
468
|
+
async delete() {
|
|
469
|
+
}
|
|
470
|
+
async deleteByLogoutToken() {
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
function applyEnterpriseConnectRestrictions(instance) {
|
|
474
|
+
const proto = Object.getPrototypeOf(instance);
|
|
475
|
+
for (const [name, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
|
|
476
|
+
if (name === "constructor") continue;
|
|
477
|
+
if (desc.get && !EC_ALLOWED_GETTERS.has(name)) {
|
|
478
|
+
Object.defineProperty(instance, name, {
|
|
479
|
+
get: () => {
|
|
480
|
+
throw new EnterpriseConnectNotSupportedError(name);
|
|
481
|
+
},
|
|
482
|
+
configurable: true
|
|
483
|
+
});
|
|
484
|
+
} else if (typeof desc.value === "function" && !EC_ALLOWED_METHODS.has(name)) {
|
|
485
|
+
instance[name] = () => {
|
|
486
|
+
throw new EnterpriseConnectNotSupportedError(name);
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
449
492
|
// src/server-client.ts
|
|
450
493
|
var normalizeDomain = (value) => {
|
|
451
494
|
const trimmed = value.trim();
|
|
@@ -484,6 +527,7 @@ var ServerClient = class {
|
|
|
484
527
|
#transactionStoreIdentifier;
|
|
485
528
|
#stateStore;
|
|
486
529
|
#stateStoreIdentifier;
|
|
530
|
+
#enterpriseConnect;
|
|
487
531
|
#authClientOptions;
|
|
488
532
|
#staticDomain;
|
|
489
533
|
#authClient;
|
|
@@ -554,16 +598,36 @@ var ServerClient = class {
|
|
|
554
598
|
}
|
|
555
599
|
constructor(options) {
|
|
556
600
|
this.#options = options;
|
|
601
|
+
this.#enterpriseConnect = !!options.enterpriseConnect;
|
|
557
602
|
this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
|
|
558
603
|
this.#transactionStoreIdentifier = this.#options.transactionIdentifier || "__a0_tx";
|
|
559
604
|
this.#transactionStore = options.transactionStore;
|
|
560
|
-
this.#
|
|
561
|
-
if (!this.#options.stateStore) {
|
|
605
|
+
if (!this.#enterpriseConnect && !this.#options.stateStore) {
|
|
562
606
|
throw new MissingRequiredArgumentError("stateStore");
|
|
563
607
|
}
|
|
608
|
+
this.#stateStore = this.#enterpriseConnect ? new NullStateStore() : this.#options.stateStore;
|
|
564
609
|
if (!this.#options.transactionStore) {
|
|
565
610
|
throw new MissingRequiredArgumentError("transactionStore");
|
|
566
611
|
}
|
|
612
|
+
if (this.#enterpriseConnect) {
|
|
613
|
+
if (typeof this.#options.domain === "function") {
|
|
614
|
+
throw new InvalidConfigurationError(
|
|
615
|
+
"Enterprise Connect requires a static domain string. DomainResolver is not supported because isFederatedDomain needs a concrete domain."
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const scope = this.#options.authorizationParams?.scope ?? "";
|
|
619
|
+
if (scope.includes("offline_access")) {
|
|
620
|
+
console.warn(
|
|
621
|
+
'[Auth0] Enterprise Connect: "offline_access" in scope has no effect. B2B Integration clients are not issued refresh tokens.'
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (this.#options.organization || this.#options.authorizationParams?.organization) {
|
|
625
|
+
console.warn(
|
|
626
|
+
'[Auth0] Enterprise Connect: static "organization" is set. The organization is resolved per login by HRD from the login_hint email domain. A static value routes all enterprise users to the same organization.'
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
applyEnterpriseConnectRestrictions(this);
|
|
630
|
+
}
|
|
567
631
|
if (typeof this.#options.domain !== "string" && typeof this.#options.domain !== "function") {
|
|
568
632
|
throw new InvalidConfigurationError("domain must be a string or resolver function");
|
|
569
633
|
}
|
|
@@ -654,6 +718,33 @@ var ServerClient = class {
|
|
|
654
718
|
const resolvedDomain = await this.#resolveDomain(storeOptions);
|
|
655
719
|
return sessionDomain === resolvedDomain;
|
|
656
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* Starts the Enterprise Connect login flow. Performs WebFinger domain discovery
|
|
723
|
+
* and, if the email domain is federated, initiates an interactive login with `login_hint`.
|
|
724
|
+
*
|
|
725
|
+
* @param options Options including the user's email and optional returnTo URL.
|
|
726
|
+
* @param storeOptions Optional options passed to the Transaction Store.
|
|
727
|
+
*
|
|
728
|
+
* @returns A URL to redirect to (federated domain) or null (not federated / invalid email).
|
|
729
|
+
*/
|
|
730
|
+
async startEnterpriseLogin(options, storeOptions) {
|
|
731
|
+
const parts = options.email.split("@");
|
|
732
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
733
|
+
const emailDomain = parts[1].toLowerCase();
|
|
734
|
+
const domain = await this.#resolveDomain(storeOptions);
|
|
735
|
+
const isFederated = await isFederatedDomain(domain, emailDomain, {
|
|
736
|
+
customFetch: this.#options.customFetch,
|
|
737
|
+
telemetry: getTelemetryConfig(this.#options.telemetry)
|
|
738
|
+
});
|
|
739
|
+
if (!isFederated) return null;
|
|
740
|
+
return this.startInteractiveLogin(
|
|
741
|
+
{
|
|
742
|
+
authorizationParams: { login_hint: options.email },
|
|
743
|
+
appState: options.returnTo ? { returnTo: options.returnTo } : void 0
|
|
744
|
+
},
|
|
745
|
+
storeOptions
|
|
746
|
+
);
|
|
747
|
+
}
|
|
657
748
|
/**
|
|
658
749
|
* Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
|
|
659
750
|
*
|
|
@@ -675,7 +766,8 @@ var ServerClient = class {
|
|
|
675
766
|
if (!redirectUri) {
|
|
676
767
|
throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
|
|
677
768
|
}
|
|
678
|
-
const
|
|
769
|
+
const rawScope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
|
|
770
|
+
const scope = this.#enterpriseConnect ? rawScope.split(" ").filter((s) => s !== "offline_access").join(" ") : rawScope;
|
|
679
771
|
const perLoginAuthParamsOrganization = typeof options?.authorizationParams?.organization === "string" ? options.authorizationParams.organization : void 0;
|
|
680
772
|
const clientAuthParamsOrganization = typeof this.#options.authorizationParams?.organization === "string" ? this.#options.authorizationParams.organization : void 0;
|
|
681
773
|
const resolvedOrganization = options?.organization ?? perLoginAuthParamsOrganization ?? this.#options.organization ?? clientAuthParamsOrganization;
|
|
@@ -717,6 +809,7 @@ var ServerClient = class {
|
|
|
717
809
|
* Takes an URL, extract the Authorization Code flow query parameters and requests a token.
|
|
718
810
|
* @param url The URl from which the query params should be extracted to exchange for a token.
|
|
719
811
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
812
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the code-for-token exchange.
|
|
720
813
|
*
|
|
721
814
|
* @throws {MissingTransactionError} When no transaction was found.
|
|
722
815
|
* @throws {TokenByCodeError} If there was an issue requesting the access token.
|
|
@@ -724,8 +817,16 @@ var ServerClient = class {
|
|
|
724
817
|
* @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
|
|
725
818
|
*
|
|
726
819
|
* @returns A promise resolving to an object, containing the original appState (if present) and the authorizationDetails (when RAR was used).
|
|
820
|
+
* In Enterprise Connect mode, also includes `idTokenClaims` and `user`.
|
|
821
|
+
*
|
|
822
|
+
* @remarks
|
|
823
|
+
* This method does not support the `fullResponse` opt-in in v1. It accepts
|
|
824
|
+
* `url` and `storeOptions` with no intermediate options object; adding
|
|
825
|
+
* `fullResponse` would require a new options parameter and is deferred to
|
|
826
|
+
* a later revision.
|
|
827
|
+
* TODO(#<issue-number>): add fullResponse overload to completeInteractiveLogin in a future minor.
|
|
727
828
|
*/
|
|
728
|
-
async completeInteractiveLogin(url, storeOptions) {
|
|
829
|
+
async completeInteractiveLogin(url, storeOptions, requestOptions) {
|
|
729
830
|
const transactionData = await this.#transactionStore.get(this.#transactionStoreIdentifier, storeOptions);
|
|
730
831
|
if (!transactionData) {
|
|
731
832
|
throw new MissingTransactionError();
|
|
@@ -736,8 +837,18 @@ var ServerClient = class {
|
|
|
736
837
|
// TransactionData.codeVerifier is optional only to accommodate magic-link transactions.
|
|
737
838
|
codeVerifier: transactionData.codeVerifier,
|
|
738
839
|
organization: transactionData.organization
|
|
739
|
-
});
|
|
840
|
+
}, requestOptions);
|
|
740
841
|
await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
|
|
842
|
+
if (this.#enterpriseConnect) {
|
|
843
|
+
const claims = tokenEndpointResponse.claims;
|
|
844
|
+
const user = claims ? Object.fromEntries(Object.entries(claims).filter(([, v]) => v !== void 0)) : void 0;
|
|
845
|
+
return {
|
|
846
|
+
appState: transactionData.appState,
|
|
847
|
+
authorizationDetails: tokenEndpointResponse.authorizationDetails,
|
|
848
|
+
idTokenClaims: claims,
|
|
849
|
+
user
|
|
850
|
+
};
|
|
851
|
+
}
|
|
741
852
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
742
853
|
const stateData = applySessionExpiryAtLogin(
|
|
743
854
|
updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
@@ -746,7 +857,10 @@ var ServerClient = class {
|
|
|
746
857
|
tokenEndpointResponse.claims
|
|
747
858
|
);
|
|
748
859
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
749
|
-
return {
|
|
860
|
+
return {
|
|
861
|
+
appState: transactionData.appState,
|
|
862
|
+
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
863
|
+
};
|
|
750
864
|
}
|
|
751
865
|
/**
|
|
752
866
|
* Starts the user linking process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
|
|
@@ -800,14 +914,15 @@ var ServerClient = class {
|
|
|
800
914
|
* Takes an URL, extract the Authorization Code flow query parameters and requests a token.
|
|
801
915
|
* @param url The URl from which the query params should be extracted to exchange for a token.
|
|
802
916
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
917
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the code-for-token exchange.
|
|
803
918
|
*
|
|
804
919
|
* @throws {MissingTransactionError} When no transaction was found.
|
|
805
920
|
* @throws {TokenByCodeError} If there was an issue requesting the access token.
|
|
806
921
|
*
|
|
807
922
|
* @returns A promise resolving to an object, containing the original appState (if present).
|
|
808
923
|
*/
|
|
809
|
-
async completeLinkUser(url, storeOptions) {
|
|
810
|
-
const result = await this.completeInteractiveLogin(url, storeOptions);
|
|
924
|
+
async completeLinkUser(url, storeOptions, requestOptions) {
|
|
925
|
+
const result = await this.completeInteractiveLogin(url, storeOptions, requestOptions);
|
|
811
926
|
return {
|
|
812
927
|
appState: result.appState
|
|
813
928
|
};
|
|
@@ -863,43 +978,44 @@ var ServerClient = class {
|
|
|
863
978
|
* Takes an URL, extract the Authorization Code flow query parameters and requests a token.
|
|
864
979
|
* @param url The URl from which the query params should be extracted to exchange for a token.
|
|
865
980
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
981
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the code-for-token exchange.
|
|
866
982
|
*
|
|
867
983
|
* @throws {MissingTransactionError} When no transaction was found.
|
|
868
984
|
* @throws {TokenByCodeError} If there was an issue requesting the access token.
|
|
869
985
|
*
|
|
870
986
|
* @returns A promise resolving to an object, containing the original appState (if present).
|
|
871
987
|
*/
|
|
872
|
-
async completeUnlinkUser(url, storeOptions) {
|
|
873
|
-
const result = await this.completeInteractiveLogin(url, storeOptions);
|
|
988
|
+
async completeUnlinkUser(url, storeOptions, requestOptions) {
|
|
989
|
+
const result = await this.completeInteractiveLogin(url, storeOptions, requestOptions);
|
|
874
990
|
return {
|
|
875
991
|
appState: result.appState
|
|
876
992
|
};
|
|
877
993
|
}
|
|
878
|
-
|
|
879
|
-
* Logs in using Client-Initiated Backchannel Authentication.
|
|
880
|
-
*
|
|
881
|
-
* Using Client-Initiated Backchannel Authentication requires the feature to be enabled in the Auth0 dashboard.
|
|
882
|
-
* @see https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow
|
|
883
|
-
* @param options Options used to configure the backchannel login process.
|
|
884
|
-
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
885
|
-
*
|
|
886
|
-
* @throws {BackchannelAuthenticationError} If there was an issue when doing backchannel authentication.
|
|
887
|
-
* @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
|
|
888
|
-
*
|
|
889
|
-
* @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
|
|
890
|
-
*/
|
|
891
|
-
async loginBackchannel(options, storeOptions) {
|
|
994
|
+
async loginBackchannel(options, storeOptions, requestOptions) {
|
|
892
995
|
const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
|
|
893
996
|
const domain = await this.#resolveDomain(storeOptions);
|
|
894
997
|
const authClient = this.#getAuthClient(domain);
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
998
|
+
let response;
|
|
999
|
+
let tokenEndpointResponse;
|
|
1000
|
+
if (options.fullResponse) {
|
|
1001
|
+
const authJsResult = await authClient.backchannelAuthentication({
|
|
1002
|
+
bindingMessage: options.bindingMessage,
|
|
1003
|
+
loginHint: options.loginHint,
|
|
1004
|
+
authorizationParams: { ...options.authorizationParams, scope },
|
|
1005
|
+
fullResponse: true
|
|
1006
|
+
}, requestOptions);
|
|
1007
|
+
tokenEndpointResponse = authJsResult.data;
|
|
1008
|
+
response = authJsResult.response;
|
|
1009
|
+
} else {
|
|
1010
|
+
tokenEndpointResponse = await authClient.backchannelAuthentication({
|
|
1011
|
+
bindingMessage: options.bindingMessage,
|
|
1012
|
+
loginHint: options.loginHint,
|
|
1013
|
+
authorizationParams: {
|
|
1014
|
+
...options.authorizationParams,
|
|
1015
|
+
scope
|
|
1016
|
+
}
|
|
1017
|
+
}, requestOptions);
|
|
1018
|
+
}
|
|
903
1019
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
904
1020
|
const stateData = applySessionExpiryAtLogin(
|
|
905
1021
|
updateStateData(this.#options.authorizationParams?.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
@@ -908,9 +1024,16 @@ var ServerClient = class {
|
|
|
908
1024
|
tokenEndpointResponse.claims
|
|
909
1025
|
);
|
|
910
1026
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
911
|
-
|
|
1027
|
+
const result = {
|
|
912
1028
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
913
1029
|
};
|
|
1030
|
+
if (options.fullResponse) {
|
|
1031
|
+
if (!response) {
|
|
1032
|
+
throw new MissingCapturedResponseError();
|
|
1033
|
+
}
|
|
1034
|
+
return { data: result, response };
|
|
1035
|
+
}
|
|
1036
|
+
return result;
|
|
914
1037
|
}
|
|
915
1038
|
/**
|
|
916
1039
|
* Starts a passwordless flow by sending a one-time code (OTP) or a magic link.
|
|
@@ -933,6 +1056,7 @@ var ServerClient = class {
|
|
|
933
1056
|
*
|
|
934
1057
|
* @param options Discriminated start options.
|
|
935
1058
|
* @param storeOptions Optional options passed to the resolver / stores.
|
|
1059
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the `/passwordless/start` request.
|
|
936
1060
|
*
|
|
937
1061
|
* @throws {PasswordlessStartError} If the request fails, or if a magic link is requested without a `redirectUri`.
|
|
938
1062
|
*
|
|
@@ -949,22 +1073,28 @@ var ServerClient = class {
|
|
|
949
1073
|
* redirectUri: 'https://app.example.com/auth/callback',
|
|
950
1074
|
* });
|
|
951
1075
|
*/
|
|
952
|
-
async startPasswordless(options, storeOptions) {
|
|
1076
|
+
async startPasswordless(options, storeOptions, requestOptions) {
|
|
953
1077
|
const domain = await this.#resolveDomain(storeOptions);
|
|
954
1078
|
const authClient = this.#getAuthClient(domain);
|
|
955
1079
|
if (options.connection === "sms") {
|
|
956
|
-
await authClient.passwordless.sendSms(
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1080
|
+
await authClient.passwordless.sendSms(
|
|
1081
|
+
{
|
|
1082
|
+
phoneNumber: options.phoneNumber,
|
|
1083
|
+
language: options.language
|
|
1084
|
+
},
|
|
1085
|
+
requestOptions
|
|
1086
|
+
);
|
|
960
1087
|
return;
|
|
961
1088
|
}
|
|
962
1089
|
if (options.send !== "link") {
|
|
963
|
-
await authClient.passwordless.sendEmail(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1090
|
+
await authClient.passwordless.sendEmail(
|
|
1091
|
+
{
|
|
1092
|
+
email: options.email,
|
|
1093
|
+
send: "code",
|
|
1094
|
+
language: options.language
|
|
1095
|
+
},
|
|
1096
|
+
requestOptions
|
|
1097
|
+
);
|
|
968
1098
|
return;
|
|
969
1099
|
}
|
|
970
1100
|
if (!options.redirectUri || typeof options.redirectUri !== "string") {
|
|
@@ -973,19 +1103,22 @@ var ServerClient = class {
|
|
|
973
1103
|
const state = crypto.randomUUID();
|
|
974
1104
|
const scope = ensureOpenIdScope(options.scope ?? this.#options.authorizationParams?.scope);
|
|
975
1105
|
const audience = options.audience ?? this.#options.authorizationParams?.audience;
|
|
976
|
-
await authClient.passwordless.sendEmail(
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1106
|
+
await authClient.passwordless.sendEmail(
|
|
1107
|
+
{
|
|
1108
|
+
email: options.email,
|
|
1109
|
+
send: "link",
|
|
1110
|
+
language: options.language,
|
|
1111
|
+
authParams: {
|
|
1112
|
+
...options.authParams,
|
|
1113
|
+
redirect_uri: options.redirectUri,
|
|
1114
|
+
response_type: "code",
|
|
1115
|
+
scope,
|
|
1116
|
+
...audience ? { audience } : {},
|
|
1117
|
+
state
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
requestOptions
|
|
1121
|
+
);
|
|
989
1122
|
const transactionState = {
|
|
990
1123
|
audience,
|
|
991
1124
|
domain,
|
|
@@ -993,42 +1126,42 @@ var ServerClient = class {
|
|
|
993
1126
|
};
|
|
994
1127
|
await this.#transactionStore.set(this.#transactionStoreIdentifier, transactionState, false, storeOptions);
|
|
995
1128
|
}
|
|
996
|
-
|
|
997
|
-
* Completes a passwordless OTP login and persists the resulting session.
|
|
998
|
-
*
|
|
999
|
-
* Discriminated on `connection` to mirror the `@auth0/nextjs-auth0` `passwordless.verify()`
|
|
1000
|
-
* surface. Non-redirect flow: no PKCE and no transaction store (mirrors
|
|
1001
|
-
* {@link ServerClient#loginBackchannel}). The `openid` scope is always ensured by this layer.
|
|
1002
|
-
*
|
|
1003
|
-
* Note: the state store is read-then-written; if your deployment performs concurrent
|
|
1004
|
-
* logins for the same session identifier, use a state store with atomic/serializable
|
|
1005
|
-
* writes to avoid last-write-wins races.
|
|
1006
|
-
*
|
|
1007
|
-
* @param options Discriminated completion options (`connection`, identifier, `verificationCode`).
|
|
1008
|
-
* @param storeOptions Optional options passed to the resolver / stores.
|
|
1009
|
-
*
|
|
1010
|
-
* @throws {PasswordlessVerifyError} If the code is invalid, expired, or rate-limited. When the
|
|
1011
|
-
* connection requires MFA, the server responds with `mfa_required`; narrow the thrown error
|
|
1012
|
-
* with `isMfaRequiredError(error)` to read `cause.mfa_token`.
|
|
1013
|
-
*
|
|
1014
|
-
* @returns A promise resolving to the authorizationDetails (when RAR was used).
|
|
1015
|
-
*/
|
|
1016
|
-
async completePasswordless(options, storeOptions) {
|
|
1129
|
+
async completePasswordless(options, storeOptions, requestOptions) {
|
|
1017
1130
|
const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
|
|
1018
1131
|
const audience = options.authorizationParams?.audience ?? this.#options.authorizationParams?.audience;
|
|
1019
1132
|
const domain = await this.#resolveDomain(storeOptions);
|
|
1020
1133
|
const authClient = this.#getAuthClient(domain);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1134
|
+
let response;
|
|
1135
|
+
let tokenEndpointResponse;
|
|
1136
|
+
if (options.fullResponse) {
|
|
1137
|
+
const authJsResult = options.connection === "sms" ? await authClient.getTokenByPasswordlessSms({
|
|
1138
|
+
phoneNumber: options.phoneNumber,
|
|
1139
|
+
code: options.verificationCode,
|
|
1140
|
+
audience,
|
|
1141
|
+
scope,
|
|
1142
|
+
fullResponse: true
|
|
1143
|
+
}, requestOptions) : await authClient.getTokenByPasswordlessEmail({
|
|
1144
|
+
email: options.email,
|
|
1145
|
+
code: options.verificationCode,
|
|
1146
|
+
audience,
|
|
1147
|
+
scope,
|
|
1148
|
+
fullResponse: true
|
|
1149
|
+
}, requestOptions);
|
|
1150
|
+
tokenEndpointResponse = authJsResult.data;
|
|
1151
|
+
response = authJsResult.response;
|
|
1152
|
+
} else {
|
|
1153
|
+
tokenEndpointResponse = options.connection === "sms" ? await authClient.getTokenByPasswordlessSms({
|
|
1154
|
+
phoneNumber: options.phoneNumber,
|
|
1155
|
+
code: options.verificationCode,
|
|
1156
|
+
audience,
|
|
1157
|
+
scope
|
|
1158
|
+
}, requestOptions) : await authClient.getTokenByPasswordlessEmail({
|
|
1159
|
+
email: options.email,
|
|
1160
|
+
code: options.verificationCode,
|
|
1161
|
+
audience,
|
|
1162
|
+
scope
|
|
1163
|
+
}, requestOptions);
|
|
1164
|
+
}
|
|
1032
1165
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1033
1166
|
const stateData = updateStateData(
|
|
1034
1167
|
this.#options.authorizationParams?.audience ?? "default",
|
|
@@ -1037,9 +1170,16 @@ var ServerClient = class {
|
|
|
1037
1170
|
{ domain }
|
|
1038
1171
|
);
|
|
1039
1172
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1040
|
-
|
|
1173
|
+
const result = {
|
|
1041
1174
|
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
1042
1175
|
};
|
|
1176
|
+
if (options.fullResponse) {
|
|
1177
|
+
if (!response) {
|
|
1178
|
+
throw new MissingCapturedResponseError();
|
|
1179
|
+
}
|
|
1180
|
+
return { data: result, response };
|
|
1181
|
+
}
|
|
1182
|
+
return result;
|
|
1043
1183
|
}
|
|
1044
1184
|
/**
|
|
1045
1185
|
* Completes a passwordless magic-link login and persists the resulting session.
|
|
@@ -1051,6 +1191,7 @@ var ServerClient = class {
|
|
|
1051
1191
|
*
|
|
1052
1192
|
* @param url The callback URL containing the authorization `code` and `state`.
|
|
1053
1193
|
* @param storeOptions Optional options passed to the resolver / stores.
|
|
1194
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the code exchange.
|
|
1054
1195
|
*
|
|
1055
1196
|
* @throws {MissingTransactionError} If no magic-link transaction was found.
|
|
1056
1197
|
* @throws {PasswordlessVerifyError} If the returned `state` is missing or does not match.
|
|
@@ -1061,7 +1202,7 @@ var ServerClient = class {
|
|
|
1061
1202
|
* @example
|
|
1062
1203
|
* const result = await serverClient.completePasswordlessMagicLink(callbackUrl, storeOptions);
|
|
1063
1204
|
*/
|
|
1064
|
-
async completePasswordlessMagicLink(url, storeOptions) {
|
|
1205
|
+
async completePasswordlessMagicLink(url, storeOptions, requestOptions) {
|
|
1065
1206
|
const transactionData = await this.#transactionStore.get(this.#transactionStoreIdentifier, storeOptions);
|
|
1066
1207
|
if (!transactionData) {
|
|
1067
1208
|
throw new MissingTransactionError();
|
|
@@ -1073,7 +1214,7 @@ var ServerClient = class {
|
|
|
1073
1214
|
}
|
|
1074
1215
|
const domain = transactionData.domain ?? await this.#resolveDomain(storeOptions);
|
|
1075
1216
|
const authClient = this.#getAuthClient(domain);
|
|
1076
|
-
const tokenEndpointResponse = await authClient.getTokenByMagicLinkCode(url, { expectedState });
|
|
1217
|
+
const tokenEndpointResponse = await authClient.getTokenByMagicLinkCode(url, { expectedState }, requestOptions);
|
|
1077
1218
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1078
1219
|
const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
|
|
1079
1220
|
domain
|
|
@@ -1086,6 +1227,12 @@ var ServerClient = class {
|
|
|
1086
1227
|
}
|
|
1087
1228
|
/**
|
|
1088
1229
|
* Retrieves the user from the store, or undefined if no user found.
|
|
1230
|
+
*
|
|
1231
|
+
* This does not accept `RequestOptions`. It is a pure read from the state store and makes no
|
|
1232
|
+
* network call, so a per-request `signal`/`headers`/`customFetch` could not take effect. The
|
|
1233
|
+
* exclusion is deliberate: a parameter that can never do anything costs the public surface more
|
|
1234
|
+
* than the asymmetry with `getAccessToken`/`getAccessTokenForConnection`/`revokeRefreshToken` does.
|
|
1235
|
+
*
|
|
1089
1236
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1090
1237
|
* @returns The user, or undefined if no user found in the store.
|
|
1091
1238
|
*/
|
|
@@ -1128,6 +1275,34 @@ var ServerClient = class {
|
|
|
1128
1275
|
return sessionData;
|
|
1129
1276
|
}
|
|
1130
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Retrieves the OIDC UserInfo claims for a given access token.
|
|
1280
|
+
*
|
|
1281
|
+
* The access token must be supplied explicitly by the caller. This method does NOT read
|
|
1282
|
+
* the token from the session and does NOT trigger a refresh. The token must be accepted by
|
|
1283
|
+
* the `/userinfo` endpoint:
|
|
1284
|
+
* - Without Multi-Resource Refresh Tokens (MRRT): pass a default OIDC access token, one
|
|
1285
|
+
* obtained without an explicit `audience` parameter.
|
|
1286
|
+
* - With MRRT: tokens are audience-bound, so request the userinfo endpoint as the audience
|
|
1287
|
+
* (e.g. `https://<domain>/userinfo`) when obtaining the token. A token bound to a
|
|
1288
|
+
* different resource-server audience is rejected by `/userinfo`.
|
|
1289
|
+
*
|
|
1290
|
+
* `/userinfo` is a bearer-protected resource and requires no client authentication, so this
|
|
1291
|
+
* works for public clients: the supplied access token is the only credential used.
|
|
1292
|
+
*
|
|
1293
|
+
* @param options Options containing the access token and an optional expected subject
|
|
1294
|
+
* for OIDC subject-consistency validation.
|
|
1295
|
+
* @param storeOptions Optional store options, used to resolve the domain in resolver mode.
|
|
1296
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch) forwarded
|
|
1297
|
+
* to the underlying `/userinfo` request.
|
|
1298
|
+
* @throws {UserInfoError} When the `/userinfo` request fails or the subject check fails.
|
|
1299
|
+
* @returns A Promise resolving to the UserInfo claims.
|
|
1300
|
+
*/
|
|
1301
|
+
async getUserInfo(options, storeOptions, requestOptions) {
|
|
1302
|
+
const domain = await this.#resolveDomain(storeOptions);
|
|
1303
|
+
const authClient = this.#getAuthClient(domain);
|
|
1304
|
+
return authClient.getUserInfo(options, requestOptions);
|
|
1305
|
+
}
|
|
1131
1306
|
/**
|
|
1132
1307
|
* Retrieves the access token from the store, or calls Auth0 when the access token is expired and a refresh token is available in the store.
|
|
1133
1308
|
* Also updates the store when a new token was retrieved from Auth0.
|
|
@@ -1136,19 +1311,30 @@ var ServerClient = class {
|
|
|
1136
1311
|
* request an access token for that audience/scope (Multi-Resource Refresh Tokens). Tokens are cached per
|
|
1137
1312
|
* audience and scope combination.
|
|
1138
1313
|
*
|
|
1139
|
-
*
|
|
1314
|
+
* When `options.fullResponse` is `true`, the method returns an {@link ApiResponse} envelope containing both
|
|
1315
|
+
* the token set and the raw {@link Response} from the token endpoint. The cache is bypassed in this case,
|
|
1316
|
+
* forcing a refresh-token call even when a valid cached token exists, because the `Response` can only be
|
|
1317
|
+
* produced by a live HTTP call.
|
|
1318
|
+
*
|
|
1319
|
+
* @param options Optional options for requesting a specific audience/scope or enabling full response.
|
|
1140
1320
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1321
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Only supported with the options form (second overload). A cache hit returns before any network call, so `requestOptions` (including `signal`) is a no-op on that path; it applies only to the refresh-token exchange on a cache miss.
|
|
1322
|
+
*
|
|
1323
|
+
* @remarks
|
|
1324
|
+
* Legacy single-argument form: `getAccessToken(storeOptions?)`. If your `TStoreOptions` type
|
|
1325
|
+
* contains `audience` or `scope` keys, use the explicit two-argument form instead:
|
|
1326
|
+
* `getAccessToken({}, storeOptions)` to avoid call-site routing ambiguity.
|
|
1141
1327
|
*
|
|
1142
1328
|
* @throws {TokenByRefreshTokenError} If the refresh token was not found or there was an issue requesting the access token. When the cause is `mfa_required`, use `isMfaRequiredError(error)` to narrow the error and read `cause.mfa_token`.
|
|
1143
1329
|
* @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and no refresh is attempted — the user must re-authenticate.
|
|
1144
1330
|
*
|
|
1145
|
-
* @returns The Token Set
|
|
1331
|
+
* @returns The Token Set when `fullResponse` is omitted, or an {@link ApiResponse} envelope when `fullResponse: true`.
|
|
1146
1332
|
*/
|
|
1147
|
-
async getAccessToken(tokenOptionsOrStoreOptions, storeOptions) {
|
|
1333
|
+
async getAccessToken(tokenOptionsOrStoreOptions, storeOptions, requestOptions) {
|
|
1148
1334
|
const hasTokenOptions = (
|
|
1149
1335
|
// If second arg exists, first arg must be GetAccessTokenOptions
|
|
1150
|
-
storeOptions !== void 0 || // OR if first arg has audience
|
|
1151
|
-
!!tokenOptionsOrStoreOptions && typeof tokenOptionsOrStoreOptions === "object" && ("audience" in tokenOptionsOrStoreOptions || "scope" in tokenOptionsOrStoreOptions)
|
|
1336
|
+
storeOptions !== void 0 || // OR if first arg has audience, scope, or fullResponse properties
|
|
1337
|
+
!!tokenOptionsOrStoreOptions && typeof tokenOptionsOrStoreOptions === "object" && ("audience" in tokenOptionsOrStoreOptions || "scope" in tokenOptionsOrStoreOptions || "fullResponse" in tokenOptionsOrStoreOptions)
|
|
1152
1338
|
);
|
|
1153
1339
|
const [resolvedOptions, resolvedStoreOptions] = hasTokenOptions ? [tokenOptionsOrStoreOptions, storeOptions] : [void 0, tokenOptionsOrStoreOptions];
|
|
1154
1340
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
|
|
@@ -1176,7 +1362,9 @@ var ServerClient = class {
|
|
|
1176
1362
|
(tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
|
|
1177
1363
|
);
|
|
1178
1364
|
if (tokenSet && tokenSet.expiresAt > Date.now() / 1e3) {
|
|
1179
|
-
|
|
1365
|
+
if (!resolvedOptions?.fullResponse) {
|
|
1366
|
+
return tokenSet;
|
|
1367
|
+
}
|
|
1180
1368
|
}
|
|
1181
1369
|
if (!stateData?.refreshToken) {
|
|
1182
1370
|
throw new TokenByRefreshTokenError(
|
|
@@ -1193,35 +1381,38 @@ var ServerClient = class {
|
|
|
1193
1381
|
...scope && { scope }
|
|
1194
1382
|
}
|
|
1195
1383
|
};
|
|
1196
|
-
|
|
1384
|
+
let response;
|
|
1385
|
+
let tokenEndpointResponse;
|
|
1386
|
+
if (resolvedOptions?.fullResponse) {
|
|
1387
|
+
const authJsResult = await this.#getAuthClient(domainForSession).getTokenByRefreshToken({
|
|
1388
|
+
...tokenByRefreshTokenOptions,
|
|
1389
|
+
fullResponse: true
|
|
1390
|
+
}, requestOptions);
|
|
1391
|
+
tokenEndpointResponse = authJsResult.data;
|
|
1392
|
+
response = authJsResult.response;
|
|
1393
|
+
} else {
|
|
1394
|
+
tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenByRefreshToken(tokenByRefreshTokenOptions, requestOptions);
|
|
1395
|
+
}
|
|
1197
1396
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
|
|
1198
1397
|
const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
|
|
1199
1398
|
domain: domainForSession
|
|
1200
1399
|
});
|
|
1201
1400
|
await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, resolvedStoreOptions);
|
|
1202
|
-
|
|
1401
|
+
const returnTokenSet = {
|
|
1203
1402
|
accessToken: tokenEndpointResponse.accessToken,
|
|
1204
1403
|
scope: tokenEndpointResponse.scope,
|
|
1205
1404
|
expiresAt: tokenEndpointResponse.expiresAt,
|
|
1206
1405
|
audience
|
|
1207
1406
|
};
|
|
1407
|
+
if (resolvedOptions?.fullResponse) {
|
|
1408
|
+
if (!response) {
|
|
1409
|
+
throw new MissingCapturedResponseError();
|
|
1410
|
+
}
|
|
1411
|
+
return { data: returnTokenSet, response };
|
|
1412
|
+
}
|
|
1413
|
+
return returnTokenSet;
|
|
1208
1414
|
}
|
|
1209
|
-
|
|
1210
|
-
* Retrieves an access token for a connection.
|
|
1211
|
-
*
|
|
1212
|
-
* This method attempts to obtain an access token for a specified connection.
|
|
1213
|
-
* It first checks if a refresh token exists in the store.
|
|
1214
|
-
* If no refresh token is found, it throws an `AccessTokenForConnectionError` indicating
|
|
1215
|
-
* that the refresh token was not found.
|
|
1216
|
-
*
|
|
1217
|
-
* @param options - Options for retrieving an access token for a connection.
|
|
1218
|
-
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1219
|
-
*
|
|
1220
|
-
* @throws {TokenForConnectionError} If the refresh token was not found or there was an issue requesting the access token.
|
|
1221
|
-
*
|
|
1222
|
-
* @returns The Connection Token Set, containing the access token for the connection, as well as additional information.
|
|
1223
|
-
*/
|
|
1224
|
-
async getAccessTokenForConnection(options, storeOptions) {
|
|
1415
|
+
async getAccessTokenForConnection(options, storeOptions, requestOptions) {
|
|
1225
1416
|
const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1226
1417
|
const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
|
|
1227
1418
|
if (this.#isResolverMode()) {
|
|
@@ -1240,7 +1431,9 @@ var ServerClient = class {
|
|
|
1240
1431
|
(tokenSet) => tokenSet.connection === options.connection
|
|
1241
1432
|
);
|
|
1242
1433
|
if (connectionTokenSet && connectionTokenSet.expiresAt > Date.now() / 1e3) {
|
|
1243
|
-
|
|
1434
|
+
if (!options.fullResponse) {
|
|
1435
|
+
return connectionTokenSet;
|
|
1436
|
+
}
|
|
1244
1437
|
}
|
|
1245
1438
|
if (!stateData?.refreshToken) {
|
|
1246
1439
|
throw new TokenForConnectionError(
|
|
@@ -1248,11 +1441,24 @@ var ServerClient = class {
|
|
|
1248
1441
|
);
|
|
1249
1442
|
}
|
|
1250
1443
|
const domainForSession = sessionDomain;
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1444
|
+
let response;
|
|
1445
|
+
let tokenEndpointResponse;
|
|
1446
|
+
if (options.fullResponse) {
|
|
1447
|
+
const authJsResult = await this.#getAuthClient(domainForSession).getTokenForConnection({
|
|
1448
|
+
connection: options.connection,
|
|
1449
|
+
loginHint: options.loginHint,
|
|
1450
|
+
refreshToken: stateData.refreshToken,
|
|
1451
|
+
fullResponse: true
|
|
1452
|
+
}, requestOptions);
|
|
1453
|
+
tokenEndpointResponse = authJsResult.data;
|
|
1454
|
+
response = authJsResult.response;
|
|
1455
|
+
} else {
|
|
1456
|
+
tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenForConnection({
|
|
1457
|
+
connection: options.connection,
|
|
1458
|
+
loginHint: options.loginHint,
|
|
1459
|
+
refreshToken: stateData.refreshToken
|
|
1460
|
+
}, requestOptions);
|
|
1461
|
+
}
|
|
1256
1462
|
const updatedStateData = updateStateDataForConnectionTokenSet(
|
|
1257
1463
|
options,
|
|
1258
1464
|
{
|
|
@@ -1262,13 +1468,20 @@ var ServerClient = class {
|
|
|
1262
1468
|
tokenEndpointResponse
|
|
1263
1469
|
);
|
|
1264
1470
|
await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
|
|
1265
|
-
|
|
1471
|
+
const returnConnectionTokenSet = {
|
|
1266
1472
|
accessToken: tokenEndpointResponse.accessToken,
|
|
1267
1473
|
scope: tokenEndpointResponse.scope,
|
|
1268
1474
|
expiresAt: tokenEndpointResponse.expiresAt,
|
|
1269
1475
|
connection: options.connection,
|
|
1270
1476
|
loginHint: options.loginHint
|
|
1271
1477
|
};
|
|
1478
|
+
if (options.fullResponse) {
|
|
1479
|
+
if (!response) {
|
|
1480
|
+
throw new MissingCapturedResponseError();
|
|
1481
|
+
}
|
|
1482
|
+
return { data: returnConnectionTokenSet, response };
|
|
1483
|
+
}
|
|
1484
|
+
return returnConnectionTokenSet;
|
|
1272
1485
|
}
|
|
1273
1486
|
/**
|
|
1274
1487
|
* Revokes the refresh token stored in the current session, or an explicitly supplied token.
|
|
@@ -1280,12 +1493,13 @@ var ServerClient = class {
|
|
|
1280
1493
|
*
|
|
1281
1494
|
* @param options Optionally supply a token to revoke instead of reading from the session.
|
|
1282
1495
|
* @param storeOptions Optional options passed to the StateStore.
|
|
1496
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the revocation request.
|
|
1283
1497
|
*
|
|
1284
1498
|
* @throws {MissingRequiredArgumentError} If `options.token` is an empty string.
|
|
1285
1499
|
* @throws {MissingSessionError} If no refresh token is found in the session and none was provided.
|
|
1286
1500
|
* @throws {TokenRevocationError} If the revocation request fails.
|
|
1287
1501
|
*/
|
|
1288
|
-
async revokeRefreshToken(options = {}, storeOptions) {
|
|
1502
|
+
async revokeRefreshToken(options = {}, storeOptions, requestOptions) {
|
|
1289
1503
|
if (options.token !== void 0 && options.token.length === 0) {
|
|
1290
1504
|
throw new MissingRequiredArgumentError("options.token must not be an empty string.");
|
|
1291
1505
|
}
|
|
@@ -1309,18 +1523,27 @@ var ServerClient = class {
|
|
|
1309
1523
|
} else {
|
|
1310
1524
|
authClient = this.authClient;
|
|
1311
1525
|
}
|
|
1312
|
-
await authClient.revokeToken({ token: refreshToken, tokenTypeHint: "refresh_token" });
|
|
1526
|
+
await authClient.revokeToken({ token: refreshToken, tokenTypeHint: "refresh_token" }, requestOptions);
|
|
1313
1527
|
}
|
|
1314
1528
|
/**
|
|
1315
1529
|
* Logs the user out and returns a URL to redirect the user-agent to after they log out.
|
|
1316
1530
|
* @param options Options used to configure the logout process.
|
|
1317
1531
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1532
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the token revocation ONLY. Building the logout URL is local string work and issues no request, so nothing here can affect it.
|
|
1318
1533
|
* @returns {URL}
|
|
1319
1534
|
*/
|
|
1320
|
-
async logout(options, storeOptions) {
|
|
1535
|
+
async logout(options, storeOptions, requestOptions) {
|
|
1536
|
+
if (this.#enterpriseConnect) {
|
|
1537
|
+
if (options.federated === false) {
|
|
1538
|
+
console.warn(
|
|
1539
|
+
"[Auth0] Enterprise Connect: logout() called with federated=false. The enterprise IdP session will remain active; the user may silently re-authenticate on the next login."
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
return this.authClient.buildLogoutUrl({ returnTo: options.returnTo, federated: options.federated ?? true });
|
|
1543
|
+
}
|
|
1321
1544
|
if (!this.#isResolverMode()) {
|
|
1322
1545
|
try {
|
|
1323
|
-
await this.revokeRefreshToken({}, storeOptions);
|
|
1546
|
+
await this.revokeRefreshToken({}, storeOptions, requestOptions);
|
|
1324
1547
|
} catch {
|
|
1325
1548
|
}
|
|
1326
1549
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
@@ -1336,39 +1559,33 @@ var ServerClient = class {
|
|
|
1336
1559
|
const domainMatches = sessionDomain === resolvedDomain;
|
|
1337
1560
|
if (domainMatches) {
|
|
1338
1561
|
try {
|
|
1339
|
-
await this.revokeRefreshToken({}, storeOptions);
|
|
1562
|
+
await this.revokeRefreshToken({}, storeOptions, requestOptions);
|
|
1340
1563
|
} catch {
|
|
1341
1564
|
}
|
|
1342
1565
|
await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
|
|
1343
1566
|
}
|
|
1344
1567
|
return authClient.buildLogoutUrl(options);
|
|
1345
1568
|
}
|
|
1346
|
-
|
|
1347
|
-
* Exchanges a custom token for Auth0 tokens and persists the resulting session (RFC 8693).
|
|
1348
|
-
*
|
|
1349
|
-
* Calls the token endpoint using the RFC 8693 Token Exchange grant, then stores the
|
|
1350
|
-
* resulting tokens in the StateStore — effectively logging the user in without an
|
|
1351
|
-
* interactive browser flow. Use this when the caller already holds a trusted external
|
|
1352
|
-
* token (e.g. a Google ID token, a legacy system token) and wants to establish an
|
|
1353
|
-
* Auth0 session from it.
|
|
1354
|
-
*
|
|
1355
|
-
* Requires a Token Exchange Profile configured in your Auth0 tenant.
|
|
1356
|
-
*
|
|
1357
|
-
* @param options Options for the custom token exchange, including the subject token and its type.
|
|
1358
|
-
* @param storeOptions Optional options passed to the StateStore.
|
|
1359
|
-
*
|
|
1360
|
-
* @throws {TokenExchangeError} If the exchange fails or the subject token is invalid.
|
|
1361
|
-
* @throws {MissingClientAuthError} If client credentials are not configured.
|
|
1362
|
-
*
|
|
1363
|
-
* @returns A promise resolving to an object containing `authorizationDetails` when RAR was used.
|
|
1364
|
-
*/
|
|
1365
|
-
async loginWithCustomTokenExchange(options, storeOptions) {
|
|
1569
|
+
async loginWithCustomTokenExchange(options, storeOptions, requestOptions) {
|
|
1366
1570
|
const domain = await this.#resolveDomain(storeOptions);
|
|
1367
1571
|
const authClient = this.#getAuthClient(domain);
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1572
|
+
let response;
|
|
1573
|
+
let tokenEndpointResponse;
|
|
1574
|
+
if (options.fullResponse) {
|
|
1575
|
+
const { fullResponse: _, ...rest } = options;
|
|
1576
|
+
const authJsResult = await authClient.exchangeToken({
|
|
1577
|
+
...rest,
|
|
1578
|
+
scope: ensureOpenIdScope(options.scope),
|
|
1579
|
+
fullResponse: true
|
|
1580
|
+
}, requestOptions);
|
|
1581
|
+
tokenEndpointResponse = authJsResult.data;
|
|
1582
|
+
response = authJsResult.response;
|
|
1583
|
+
} else {
|
|
1584
|
+
tokenEndpointResponse = await authClient.exchangeToken({
|
|
1585
|
+
...options,
|
|
1586
|
+
scope: ensureOpenIdScope(options.scope)
|
|
1587
|
+
}, requestOptions);
|
|
1588
|
+
}
|
|
1372
1589
|
const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
|
|
1373
1590
|
const stateData = updateStateData(
|
|
1374
1591
|
this.#options.authorizationParams?.audience ?? "default",
|
|
@@ -1377,30 +1594,25 @@ var ServerClient = class {
|
|
|
1377
1594
|
{ domain }
|
|
1378
1595
|
);
|
|
1379
1596
|
await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
|
|
1380
|
-
|
|
1597
|
+
const result = {
|
|
1598
|
+
authorizationDetails: tokenEndpointResponse.authorizationDetails
|
|
1599
|
+
};
|
|
1600
|
+
if (options.fullResponse) {
|
|
1601
|
+
if (!response) {
|
|
1602
|
+
throw new MissingCapturedResponseError();
|
|
1603
|
+
}
|
|
1604
|
+
return { data: result, response };
|
|
1605
|
+
}
|
|
1606
|
+
return result;
|
|
1381
1607
|
}
|
|
1382
|
-
|
|
1383
|
-
* Exchanges a custom token for Auth0 tokens without establishing a session (RFC 8693).
|
|
1384
|
-
*
|
|
1385
|
-
* Performs the same RFC 8693 Token Exchange as `loginWithCustomTokenExchange` but
|
|
1386
|
-
* returns the raw token response without writing anything to the StateStore. Use this
|
|
1387
|
-
* for delegation or impersonation flows where you need downstream tokens but do not
|
|
1388
|
-
* want to create or modify the current user session.
|
|
1389
|
-
*
|
|
1390
|
-
* Requires a Token Exchange Profile configured in your Auth0 tenant.
|
|
1391
|
-
*
|
|
1392
|
-
* @param options Options for the custom token exchange, including the subject token and its type.
|
|
1393
|
-
* @param storeOptions Optional options passed to the StateStore (used only for domain resolution in resolver mode).
|
|
1394
|
-
*
|
|
1395
|
-
* @throws {TokenExchangeError} If the exchange fails or the subject token is invalid.
|
|
1396
|
-
* @throws {MissingClientAuthError} If client credentials are not configured.
|
|
1397
|
-
*
|
|
1398
|
-
* @returns A promise resolving to the token response from Auth0.
|
|
1399
|
-
*/
|
|
1400
|
-
async customTokenExchange(options, storeOptions) {
|
|
1608
|
+
async customTokenExchange(options, storeOptions, requestOptions) {
|
|
1401
1609
|
const domain = await this.#resolveDomain(storeOptions);
|
|
1402
1610
|
const authClient = this.#getAuthClient(domain);
|
|
1403
|
-
|
|
1611
|
+
if (options.fullResponse) {
|
|
1612
|
+
const { fullResponse: _, ...rest } = options;
|
|
1613
|
+
return authClient.exchangeToken({ ...rest, fullResponse: true }, requestOptions);
|
|
1614
|
+
}
|
|
1615
|
+
return authClient.exchangeToken(options, requestOptions);
|
|
1404
1616
|
}
|
|
1405
1617
|
/**
|
|
1406
1618
|
* Requests a Session Transfer Token (STT) for impersonation via session transfer (RFC 8693).
|
|
@@ -1424,8 +1636,17 @@ var ServerClient = class {
|
|
|
1424
1636
|
* {@link ServerClient.buildSessionTransferRedirect}, which is forwarded to the target's
|
|
1425
1637
|
* `/authorize` on the redirect.
|
|
1426
1638
|
*
|
|
1639
|
+
* @remarks
|
|
1640
|
+
* If the actor's ID token has expired, an internal refresh is performed before the
|
|
1641
|
+
* session transfer token exchange. This refresh call is NOT guarded by the caller's
|
|
1642
|
+
* requestOptions.signal — if the signal fires during this step, the abort is ignored.
|
|
1643
|
+
* Only the final exchangeToken call respects the signal.
|
|
1644
|
+
* Thread requestOptions into #resolveSessionTransferActor in a future minor if
|
|
1645
|
+
* callers need full-request abort coverage.
|
|
1646
|
+
*
|
|
1427
1647
|
* @param options Options including the developer-supplied `subjectToken`/`subjectTokenType` and an optional explicit `actor`.
|
|
1428
1648
|
* @param storeOptions Optional options used to read the agent session (for the actor) and resolve the request domain.
|
|
1649
|
+
* @param requestOptions Optional per-request options (signal, headers, customFetch). Applied to the STT exchange only. Resolving the actor may refresh an expired agent session ID token, and that refresh is an internal call outside the caller's per-request scope, so it does not receive these options.
|
|
1429
1650
|
*
|
|
1430
1651
|
* @throws {TokenExchangeError} With code `actor_unavailable` when no explicit actor is given and no usable session ID token can be resolved — no logged-in agent, a session that belongs to a different domain in resolver mode, or an expired ID token that cannot be refreshed (raised client-side, before any network call). With the default code when the exchange itself fails; a server-side `setactor_required` or `session_transfer_disabled` condition is surfaced via `cause.error` / `cause.error_description`. An organization the tenant rejects also surfaces here.
|
|
1431
1652
|
* @throws {MissingClientAuthError} When client credentials are not configured (STT requires a confidential client).
|
|
@@ -1434,7 +1655,7 @@ var ServerClient = class {
|
|
|
1434
1655
|
*
|
|
1435
1656
|
* @returns A promise resolving to a {@link SessionTransferTokenResult} containing the STT and its metadata.
|
|
1436
1657
|
*/
|
|
1437
|
-
async requestSessionTransferToken(options, storeOptions) {
|
|
1658
|
+
async requestSessionTransferToken(options, storeOptions, requestOptions) {
|
|
1438
1659
|
if (!options.subjectToken || !options.subjectToken.trim()) {
|
|
1439
1660
|
throw new MissingRequiredArgumentError("subjectToken");
|
|
1440
1661
|
}
|
|
@@ -1460,7 +1681,7 @@ var ServerClient = class {
|
|
|
1460
1681
|
// `/authorize`; neither implies the other.
|
|
1461
1682
|
organization: options.organization,
|
|
1462
1683
|
extra: options.extra
|
|
1463
|
-
});
|
|
1684
|
+
}, requestOptions);
|
|
1464
1685
|
return {
|
|
1465
1686
|
sessionTransferToken: response.accessToken,
|
|
1466
1687
|
// Surface exactly what the server returned — never fabricate the URN, so a non-STT
|
|
@@ -1584,6 +1805,11 @@ var ServerClient = class {
|
|
|
1584
1805
|
}
|
|
1585
1806
|
/**
|
|
1586
1807
|
* Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
|
|
1808
|
+
*
|
|
1809
|
+
* This does not accept `RequestOptions`. Verification does fetch JWKS, but the auth-js method it
|
|
1810
|
+
* delegates to, `verifyLogoutToken`, takes no `requestOptions`, so there is nothing to forward.
|
|
1811
|
+
* That fetch always uses the client's configured `customFetch`.
|
|
1812
|
+
*
|
|
1587
1813
|
* @param logoutToken The logout token to verify and use to delete the session from the store.
|
|
1588
1814
|
* @param storeOptions Optional options used to pass to the Transaction and State Store.
|
|
1589
1815
|
*
|
|
@@ -1743,7 +1969,10 @@ import {
|
|
|
1743
1969
|
OrganizationValidationError as OrganizationValidationError3,
|
|
1744
1970
|
PasswordlessStartError as PasswordlessStartError2,
|
|
1745
1971
|
PasswordlessVerifyError as PasswordlessVerifyError2,
|
|
1746
|
-
|
|
1972
|
+
EnterpriseConnectNotSupportedError as EnterpriseConnectNotSupportedError2,
|
|
1973
|
+
isMfaRequiredError as isMfaRequiredError2,
|
|
1974
|
+
isFederatedDomain as isFederatedDomain2,
|
|
1975
|
+
UserInfoError
|
|
1747
1976
|
} from "@auth0/auth0-auth-js";
|
|
1748
1977
|
|
|
1749
1978
|
// src/store/cookie-transaction-store.ts
|
|
@@ -1965,6 +2194,7 @@ export {
|
|
|
1965
2194
|
BackchannelLogoutError,
|
|
1966
2195
|
ChangePasswordError,
|
|
1967
2196
|
CookieTransactionStore,
|
|
2197
|
+
EnterpriseConnectNotSupportedError2 as EnterpriseConnectNotSupportedError,
|
|
1968
2198
|
InvalidConfigurationError,
|
|
1969
2199
|
IssuerValidationError,
|
|
1970
2200
|
MfaChallengeError,
|
|
@@ -1993,6 +2223,8 @@ export {
|
|
|
1993
2223
|
TokenExchangeError2 as TokenExchangeError,
|
|
1994
2224
|
TokenExchangeErrorCode,
|
|
1995
2225
|
TokenRevocationError,
|
|
2226
|
+
UserInfoError,
|
|
2227
|
+
isFederatedDomain2 as isFederatedDomain,
|
|
1996
2228
|
isMfaRequiredError2 as isMfaRequiredError
|
|
1997
2229
|
};
|
|
1998
2230
|
//# sourceMappingURL=index.js.map
|