@auth0/auth0-server-js 1.15.0 → 1.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/dist/index.cjs CHANGED
@@ -20,28 +20,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AbstractAnonymousStore: () => AbstractAnonymousStore,
23
24
  AbstractStateStore: () => AbstractStateStore,
24
25
  AbstractTransactionStore: () => AbstractTransactionStore,
26
+ AnonymousSessionError: () => import_auth0_auth_js6.AnonymousSessionError,
27
+ AnonymousSessionExpiredError: () => AnonymousSessionExpiredError,
25
28
  BackchannelLogoutError: () => BackchannelLogoutError,
26
29
  ChangePasswordError: () => import_auth0_auth_js5.ChangePasswordError,
27
30
  CookieTransactionStore: () => CookieTransactionStore,
28
- EnterpriseConnectNotSupportedError: () => import_auth0_auth_js6.EnterpriseConnectNotSupportedError,
31
+ DEFAULT_ANONYMOUS_SESSION_LIFETIME: () => DEFAULT_ANONYMOUS_SESSION_LIFETIME,
32
+ EnterpriseConnectNotSupportedError: () => import_auth0_auth_js7.EnterpriseConnectNotSupportedError,
29
33
  InvalidConfigurationError: () => InvalidConfigurationError,
30
34
  IssuerValidationError: () => IssuerValidationError,
31
35
  MfaChallengeError: () => import_auth0_auth_js3.MfaChallengeError,
32
36
  MfaEnrollmentError: () => import_auth0_auth_js3.MfaEnrollmentError,
33
37
  MfaListAuthenticatorsError: () => import_auth0_auth_js3.MfaListAuthenticatorsError,
34
38
  MfaVerifyError: () => import_auth0_auth_js3.MfaVerifyError,
35
- MissingClientAuthError: () => import_auth0_auth_js6.MissingClientAuthError,
39
+ MissingAnonymousSessionError: () => MissingAnonymousSessionError,
40
+ MissingClientAuthError: () => import_auth0_auth_js7.MissingClientAuthError,
36
41
  MissingRequiredArgumentError: () => MissingRequiredArgumentError,
37
42
  MissingSessionError: () => MissingSessionError,
38
43
  MissingTransactionError: () => MissingTransactionError,
39
- OrganizationValidationError: () => import_auth0_auth_js6.OrganizationValidationError,
44
+ OrganizationValidationError: () => import_auth0_auth_js7.OrganizationValidationError,
40
45
  PasskeyChallengeError: () => import_auth0_auth_js4.PasskeyChallengeError,
41
46
  PasskeyGetTokenError: () => import_auth0_auth_js4.PasskeyGetTokenError,
42
47
  PasskeyRegisterError: () => import_auth0_auth_js4.PasskeyRegisterError,
43
- PasswordlessStartError: () => import_auth0_auth_js6.PasswordlessStartError,
44
- PasswordlessVerifyError: () => import_auth0_auth_js6.PasswordlessVerifyError,
48
+ PasswordlessStartError: () => import_auth0_auth_js7.PasswordlessStartError,
49
+ PasswordlessVerifyError: () => import_auth0_auth_js7.PasswordlessVerifyError,
50
+ ServerAnonymousClient: () => ServerAnonymousClient,
45
51
  ServerClient: () => ServerClient,
46
52
  ServerDatabaseClient: () => ServerDatabaseClient,
47
53
  ServerMfaClient: () => ServerMfaClient,
@@ -50,13 +56,14 @@ __export(index_exports, {
50
56
  SignUpError: () => import_auth0_auth_js5.SignUpError,
51
57
  StartLinkUserError: () => StartLinkUserError,
52
58
  StatefulStateStore: () => StatefulStateStore,
59
+ StatelessAnonymousStore: () => StatelessAnonymousStore,
53
60
  StatelessStateStore: () => StatelessStateStore,
54
- TokenExchangeError: () => import_auth0_auth_js6.TokenExchangeError,
61
+ TokenExchangeError: () => import_auth0_auth_js7.TokenExchangeError,
55
62
  TokenExchangeErrorCode: () => TokenExchangeErrorCode,
56
- TokenRevocationError: () => import_auth0_auth_js6.TokenRevocationError,
57
- UserInfoError: () => import_auth0_auth_js6.UserInfoError,
58
- isFederatedDomain: () => import_auth0_auth_js6.isFederatedDomain,
59
- isMfaRequiredError: () => import_auth0_auth_js6.isMfaRequiredError
63
+ TokenRevocationError: () => import_auth0_auth_js7.TokenRevocationError,
64
+ UserInfoError: () => import_auth0_auth_js7.UserInfoError,
65
+ isFederatedDomain: () => import_auth0_auth_js7.isFederatedDomain,
66
+ isMfaRequiredError: () => import_auth0_auth_js7.isMfaRequiredError
60
67
  });
61
68
  module.exports = __toCommonJS(index_exports);
62
69
 
@@ -115,6 +122,22 @@ var IssuerValidationError = class extends Error {
115
122
  this.name = "IssuerValidationError";
116
123
  }
117
124
  };
125
+ var MissingAnonymousSessionError = class extends Error {
126
+ code = "missing_anonymous_session_error";
127
+ constructor(message) {
128
+ super(message ?? "There is no anonymous session. Call `anonymous.createSession()` first.");
129
+ this.name = "MissingAnonymousSessionError";
130
+ }
131
+ };
132
+ var AnonymousSessionExpiredError = class extends Error {
133
+ code = "anonymous_session_expired";
134
+ constructor(message) {
135
+ super(
136
+ message ?? "The anonymous session has expired or is no longer valid. Call `anonymous.createSession()` to start a new one."
137
+ );
138
+ this.name = "AnonymousSessionExpiredError";
139
+ }
140
+ };
118
141
  var SessionExpiredError = class extends Error {
119
142
  code = "session_expired";
120
143
  constructor(message) {
@@ -255,7 +278,7 @@ var compareScopes = (scopes, requiredScopes) => {
255
278
  };
256
279
 
257
280
  // src/server-client.ts
258
- var import_jose = require("jose");
281
+ var import_jose2 = require("jose");
259
282
 
260
283
  // src/telemetry.ts
261
284
  function getTelemetryConfig(config) {
@@ -265,7 +288,7 @@ function getTelemetryConfig(config) {
265
288
  return {
266
289
  enabled: true,
267
290
  name: config?.name ?? "@auth0/auth0-server-js",
268
- version: config?.version ?? "1.15.0"
291
+ version: config?.version ?? "1.16.0"
269
292
  };
270
293
  }
271
294
 
@@ -339,6 +362,7 @@ var ServerMfaClient = class {
339
362
  true,
340
363
  storeOptions
341
364
  );
365
+ await this.#options.onUserSessionEstablished?.(storeOptions);
342
366
  const result = {
343
367
  accessToken: tokenResponse.accessToken,
344
368
  tokenType: tokenResponse.tokenType ?? "bearer",
@@ -447,6 +471,7 @@ var ServerPasskeyClient = class {
447
471
  const existingStateData = await this.#options.stateStore.get(this.#options.stateStoreIdentifier, storeOptions);
448
472
  const stateData = updateStateData(audience ?? "default", existingStateData, tokenEndpointResponse, { domain });
449
473
  await this.#options.stateStore.set(this.#options.stateStoreIdentifier, stateData, true, storeOptions);
474
+ await this.#options.onUserSessionEstablished?.(storeOptions);
450
475
  return {
451
476
  authorizationDetails: tokenEndpointResponse.authorizationDetails
452
477
  };
@@ -541,6 +566,225 @@ function applyEnterpriseConnectRestrictions(instance) {
541
566
  }
542
567
  }
543
568
 
569
+ // src/anonymous/server-anonymous-client.ts
570
+ var import_jose = require("jose");
571
+ var DEFAULT_AUDIENCE_CACHE_KEY = "default";
572
+ var readAnonymousSub = (accessToken) => {
573
+ try {
574
+ const { sub } = (0, import_jose.decodeJwt)(accessToken);
575
+ return typeof sub === "string" ? sub : void 0;
576
+ } catch {
577
+ return void 0;
578
+ }
579
+ };
580
+ var cacheKeyScope = (tokenSet) => tokenSet.requestedScope ?? tokenSet.scope;
581
+ var upsertTokenSet = (tokenSets, tokenSet) => {
582
+ const matches = (candidate) => candidate.audience === tokenSet.audience && cacheKeyScope(candidate) === cacheKeyScope(tokenSet);
583
+ return tokenSets.some(matches) ? tokenSets.map((candidate) => matches(candidate) ? tokenSet : candidate) : [...tokenSets, tokenSet];
584
+ };
585
+ var ServerAnonymousClient = class {
586
+ #options;
587
+ /**
588
+ * @internal
589
+ */
590
+ constructor(options) {
591
+ this.#options = options;
592
+ }
593
+ /**
594
+ * Creates an anonymous session and stores it, returning the first anonymous access token.
595
+ *
596
+ * Replaces any anonymous session already stored for this visitor. Metadata can only be
597
+ * attached here, because Auth0 rejects a request that carries both metadata and an
598
+ * existing session.
599
+ *
600
+ * The anonymous identity (`sub`) is read off the first access token and stored alongside
601
+ * the metadata, so {@link ServerAnonymousClient.getSession} can hand both back without the
602
+ * application decoding a token. It stays `undefined` for an audience with token encryption
603
+ * (`token_encryption`) enabled, whose access token is an encrypted JWE only the API can
604
+ * read.
605
+ *
606
+ * @param options Optional audience, scope and metadata for the new session.
607
+ * @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
608
+ *
609
+ * @throws {AnonymousSessionError} If Auth0 rejected the request. Common codes are
610
+ * `feature_not_enabled` (the tenant flag is off), `unauthorized_client` (the client is
611
+ * not enabled for anonymous sessions), `invalid_target` (the resource server does not
612
+ * allow anonymous access), `invalid_request` (metadata over 1024 bytes, or not all
613
+ * strings) and `access_denied`. `code` is `server_error` when Auth0 answers with a body
614
+ * that is not JSON.
615
+ *
616
+ * Only call this once you have established the visitor has no anonymous session (check
617
+ * {@link ServerAnonymousClient.getSession} first), rather than on every request.
618
+ *
619
+ * @returns The anonymous access token for the requested audience.
620
+ */
621
+ async createSession(options, storeOptions) {
622
+ const domain = await this.#options.resolveDomain(storeOptions);
623
+ const requestedAudience = options?.audience ?? this.#options.defaultAudience;
624
+ const audience = requestedAudience ?? DEFAULT_AUDIENCE_CACHE_KEY;
625
+ const scope = options?.scope;
626
+ const session = await this.#options.getAuthClient(domain).anonymous.createSession({
627
+ ...requestedAudience && { audience: requestedAudience },
628
+ ...scope && { scope },
629
+ ...options?.metadata && { metadata: options.metadata }
630
+ });
631
+ const tokenSet = {
632
+ audience,
633
+ accessToken: session.accessToken,
634
+ scope: session.scope,
635
+ ...scope && scope !== session.scope && { requestedScope: scope },
636
+ expiresAt: session.expiresAt
637
+ };
638
+ const stateData = {
639
+ sessionToken: session.sessionToken,
640
+ sessionTokenExpiresAt: session.sessionTokenExpiresAt,
641
+ sub: readAnonymousSub(session.accessToken),
642
+ ...options?.metadata && { metadata: options.metadata },
643
+ createdAt: Math.floor(Date.now() / 1e3),
644
+ tokenSets: [tokenSet],
645
+ domain
646
+ };
647
+ await this.#options.anonymousStore.set(
648
+ this.#options.anonymousStoreIdentifier,
649
+ stateData,
650
+ true,
651
+ storeOptions
652
+ );
653
+ return tokenSet;
654
+ }
655
+ /**
656
+ * Returns an anonymous access token for the stored anonymous session, fetching a fresh
657
+ * one from Auth0 when the cached one has expired.
658
+ *
659
+ * Tokens are cached per audience and scope, so requesting a second audience returns a
660
+ * second token for the same anonymous identity without replacing the first.
661
+ *
662
+ * Auth0 may grant fewer scopes than requested — a scope the anonymous identity is not
663
+ * entitled to is silently dropped and the response is still a success. Always check
664
+ * `tokenSet.scope` before calling your API; do not assume the token carries every scope
665
+ * you asked for.
666
+ *
667
+ * This never creates a session. If the anonymous session has expired, the stored session
668
+ * is deleted and `AnonymousSessionExpiredError` is thrown, so a visitor is never moved
669
+ * onto a fresh anonymous identity behind your back.
670
+ *
671
+ * @param options Optional audience and scope for the requested token.
672
+ * @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
673
+ *
674
+ * @throws {MissingAnonymousSessionError} When there is no anonymous session stored, or the stored one belongs to another Auth0 domain (resolver mode).
675
+ * @throws {AnonymousSessionExpiredError} When the anonymous session has expired or Auth0 rejected the session token. The stored session is deleted first.
676
+ *
677
+ * @returns The anonymous access token for the requested audience.
678
+ */
679
+ async getAccessToken(options, storeOptions) {
680
+ const stateData = await this.#options.anonymousStore.get(
681
+ this.#options.anonymousStoreIdentifier,
682
+ storeOptions
683
+ );
684
+ if (!stateData?.sessionToken) {
685
+ throw new MissingAnonymousSessionError();
686
+ }
687
+ const domain = await this.#options.resolveDomain(storeOptions);
688
+ if (this.#options.isResolverMode() && stateData.domain !== domain) {
689
+ throw new MissingAnonymousSessionError(
690
+ "The stored anonymous session was created for a different Auth0 domain."
691
+ );
692
+ }
693
+ const requestedAudience = options?.audience ?? this.#options.defaultAudience;
694
+ const audience = requestedAudience ?? DEFAULT_AUDIENCE_CACHE_KEY;
695
+ const scope = options?.scope;
696
+ const cachedTokenSet = stateData.tokenSets.find(
697
+ (tokenSet2) => tokenSet2.audience === audience && (!scope || cacheKeyScope(tokenSet2) === scope || compareScopes(tokenSet2.scope, scope))
698
+ );
699
+ if (cachedTokenSet && cachedTokenSet.expiresAt > Date.now() / 1e3) {
700
+ return cachedTokenSet;
701
+ }
702
+ const renewed = await this.#options.getAuthClient(domain).anonymous.getAccessToken({
703
+ sessionToken: stateData.sessionToken,
704
+ ...requestedAudience && { audience: requestedAudience },
705
+ ...scope && { scope }
706
+ });
707
+ if (renewed.sessionReplaced) {
708
+ await this.#options.anonymousStore.delete(this.#options.anonymousStoreIdentifier, storeOptions);
709
+ throw new AnonymousSessionExpiredError();
710
+ }
711
+ const tokenSet = {
712
+ audience,
713
+ accessToken: renewed.accessToken,
714
+ scope: renewed.scope,
715
+ ...scope && scope !== renewed.scope && { requestedScope: scope },
716
+ expiresAt: renewed.expiresAt
717
+ };
718
+ const existingStateData = await this.#options.anonymousStore.get(
719
+ this.#options.anonymousStoreIdentifier,
720
+ storeOptions
721
+ );
722
+ if (existingStateData?.sessionToken === stateData.sessionToken) {
723
+ await this.#options.anonymousStore.set(
724
+ this.#options.anonymousStoreIdentifier,
725
+ {
726
+ ...existingStateData,
727
+ ...existingStateData.sub === void 0 && { sub: readAnonymousSub(renewed.accessToken) },
728
+ tokenSets: upsertTokenSet(existingStateData.tokenSets, tokenSet)
729
+ },
730
+ false,
731
+ storeOptions
732
+ );
733
+ }
734
+ return tokenSet;
735
+ }
736
+ /**
737
+ * Returns the stored anonymous session, or `undefined` when there is none.
738
+ *
739
+ * Two common uses:
740
+ * - **Gate `createSession`** — call this first; only create a session when the result is `undefined`.
741
+ * - **Read identity for a merge** — `sub` and `metadata` are available here without decoding a token.
742
+ *
743
+ * This is a local read of the store with no request to Auth0. It cannot tell you whether
744
+ * Auth0 still considers the session valid — only {@link ServerAnonymousClient.getAccessToken} can.
745
+ * The session token is never included in the result.
746
+ *
747
+ * @param storeOptions Optional options used to pass to the anonymous store (and to resolve the domain in resolver mode).
748
+ *
749
+ * @returns The anonymous session, or `undefined` when there is none for this visitor (or it belongs to another Auth0 domain in resolver mode).
750
+ */
751
+ async getSession(storeOptions) {
752
+ const stateData = await this.#options.anonymousStore.get(
753
+ this.#options.anonymousStoreIdentifier,
754
+ storeOptions
755
+ );
756
+ if (!stateData) {
757
+ return;
758
+ }
759
+ if (this.#options.isResolverMode()) {
760
+ const domain = await this.#options.resolveDomain(storeOptions);
761
+ if (stateData.domain !== domain) {
762
+ return;
763
+ }
764
+ }
765
+ const { sessionToken, ...sessionData } = stateData;
766
+ return sessionData;
767
+ }
768
+ /**
769
+ * Clears the anonymous session from the store.
770
+ *
771
+ * Does not call `POST /anonymous/logout` — that endpoint only clears the `auth0_anon`
772
+ * browser cookie, which your server never holds. Any access tokens already issued remain
773
+ * valid until they expire (~2 hours by default).
774
+ *
775
+ * You rarely need to call this directly: every method that establishes a user session
776
+ * clears the anonymous session automatically (unless `clearAnonymousSessionOnLogin: false`
777
+ * is set), and `serverClient.logout()` clears it too. Call this to explicitly reset a
778
+ * visitor's anonymous identity, or to clean up after your own post-login merge logic when
779
+ * automatic clearing is disabled.
780
+ *
781
+ * @param storeOptions Optional options used to pass to the anonymous store.
782
+ */
783
+ async logout(storeOptions) {
784
+ await this.#options.anonymousStore.delete(this.#options.anonymousStoreIdentifier, storeOptions);
785
+ }
786
+ };
787
+
544
788
  // src/server-client.ts
545
789
  var normalizeDomain = (value) => {
546
790
  const trimmed = value.trim();
@@ -549,7 +793,7 @@ var normalizeDomain = (value) => {
549
793
  };
550
794
  var decodeIssuer = (token) => {
551
795
  try {
552
- const { iss } = (0, import_jose.decodeJwt)(token);
796
+ const { iss } = (0, import_jose2.decodeJwt)(token);
553
797
  return typeof iss === "string" ? iss : void 0;
554
798
  } catch {
555
799
  return void 0;
@@ -564,7 +808,7 @@ var actorUnavailableError = (message) => {
564
808
  };
565
809
  var isTokenExpired = (token) => {
566
810
  try {
567
- const { exp } = (0, import_jose.decodeJwt)(token);
811
+ const { exp } = (0, import_jose2.decodeJwt)(token);
568
812
  if (typeof exp !== "number") {
569
813
  return true;
570
814
  }
@@ -586,6 +830,7 @@ var ServerClient = class {
586
830
  #mfaClient;
587
831
  #passkeyClient;
588
832
  #databaseClient;
833
+ #anonymousClient;
589
834
  /**
590
835
  * The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
591
836
  * Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
@@ -648,6 +893,42 @@ var ServerClient = class {
648
893
  get database() {
649
894
  return this.#databaseClient;
650
895
  }
896
+ /**
897
+ * The anonymous session client, for giving a visitor who has not logged in a stable
898
+ * identity and an access token for your API.
899
+ *
900
+ * Provides `createSession()` to establish the anonymous identity, `getAccessToken()` to
901
+ * obtain and renew anonymous access tokens, `getSession()` to check whether a visitor
902
+ * already has one, and `logout()` to discard it.
903
+ *
904
+ * Requires `anonymousStore` on the `ServerClient`, and a tenant and client configured for
905
+ * anonymous sessions. Anonymous sessions are kept in that store, never in the state
906
+ * store, so `getSession()` and `getUser()` still report no user while one is active.
907
+ *
908
+ * The anonymous session ends when the visitor logs in: every login method clears it once
909
+ * the user session is written, unless you set `clearAnonymousSessionOnLogin: false`.
910
+ * {@link ServerClient.logout} clears it too.
911
+ *
912
+ * For `/authorize` flows, `startInteractiveLogin()` links the anonymous session to
913
+ * the user automatically via a Session Transfer Ticket — no extra configuration needed.
914
+ * For logins that bypass `/authorize` (passkey, passwordless, backchannel, custom token
915
+ * exchange), the anonymous session is not linked. Read `anonymous.getSession()` before
916
+ * the login to get the anonymous `sub` for a manual merge, or set
917
+ * `clearAnonymousSessionOnLogin: false` and clear it yourself afterwards.
918
+ *
919
+ * Like `passkey` and `database`, this works in both static and resolver (multi-tenant)
920
+ * domain modes.
921
+ *
922
+ * @throws {InvalidConfigurationError} When no `anonymousStore` is configured.
923
+ */
924
+ get anonymous() {
925
+ if (!this.#anonymousClient) {
926
+ throw new InvalidConfigurationError(
927
+ "anonymous is only available when an `anonymousStore` is configured on the ServerClient."
928
+ );
929
+ }
930
+ return this.#anonymousClient;
931
+ }
651
932
  constructor(options) {
652
933
  this.#options = options;
653
934
  this.#enterpriseConnect = !!options.enterpriseConnect;
@@ -706,7 +987,8 @@ var ServerClient = class {
706
987
  domain,
707
988
  stateStore: this.#stateStore,
708
989
  stateStoreIdentifier: this.#stateStoreIdentifier,
709
- defaultAudience: this.#options.authorizationParams?.audience ?? "default"
990
+ defaultAudience: this.#options.authorizationParams?.audience ?? "default",
991
+ onUserSessionEstablished: (storeOptions) => this.#clearAnonymousSessionAfterLogin(storeOptions)
710
992
  });
711
993
  }
712
994
  this.#passkeyClient = new ServerPasskeyClient({
@@ -715,12 +997,26 @@ var ServerClient = class {
715
997
  stateStore: this.#stateStore,
716
998
  stateStoreIdentifier: this.#stateStoreIdentifier,
717
999
  defaultScope: this.#options.authorizationParams?.scope,
718
- defaultAudience: this.#options.authorizationParams?.audience
1000
+ defaultAudience: this.#options.authorizationParams?.audience,
1001
+ onUserSessionEstablished: (storeOptions) => this.#clearAnonymousSessionAfterLogin(storeOptions)
719
1002
  });
720
1003
  this.#databaseClient = new ServerDatabaseClient({
721
1004
  resolveDomain: (storeOptions) => this.#resolveDomain(storeOptions),
722
1005
  getAuthClient: (domain) => this.#getAuthClient(domain)
723
1006
  });
1007
+ if (this.#options.anonymousStore) {
1008
+ this.#anonymousClient = new ServerAnonymousClient({
1009
+ resolveDomain: (storeOptions) => this.#resolveDomain(storeOptions),
1010
+ getAuthClient: (domain) => this.#getAuthClient(domain),
1011
+ isResolverMode: () => this.#isResolverMode(),
1012
+ anonymousStore: this.#options.anonymousStore,
1013
+ anonymousStoreIdentifier: this.#options.anonymousSessionIdentifier || "__a0_anon",
1014
+ // Only the audience is inherited from the client-level authorizationParams. The
1015
+ // scope there is written for a user session (`openid profile email offline_access`)
1016
+ // and none of it applies to an anonymous identity.
1017
+ defaultAudience: this.#options.authorizationParams?.audience
1018
+ });
1019
+ }
724
1020
  }
725
1021
  async #resolveDomain(storeOptions) {
726
1022
  if (typeof this.#options.domain === "function") {
@@ -797,6 +1093,54 @@ var ServerClient = class {
797
1093
  storeOptions
798
1094
  );
799
1095
  }
1096
+ /**
1097
+ * Discards the anonymous session once a login has established a user session.
1098
+ *
1099
+ * Called by every method that writes a user session, always AFTER the state store has been
1100
+ * written. An anonymous session outliving the login would leave a 30-day bearer credential
1101
+ * in a cookie on every request, and `anonymous.getAccessToken()` would keep minting
1102
+ * anonymous tokens for a visitor who is no longer anonymous.
1103
+ *
1104
+ * Two deliberate properties:
1105
+ * - **Best-effort.** The user is authenticated and their session is already persisted by
1106
+ * this point, so a failing anonymous store must not turn a successful login into an
1107
+ * error. The visitor would be unable to log in at all because of leftover state they
1108
+ * cannot see or clear.
1109
+ * - **Unconditional across domains.** In resolver (multi-tenant) mode the anonymous session
1110
+ * is dropped even when it was created against a different Auth0 domain than the one just
1111
+ * logged into. The anonymous store holds a single session per visitor, and "no anonymous
1112
+ * session survives a login" is a rule that can be reasoned about; a domain-dependent one
1113
+ * cannot.
1114
+ *
1115
+ * Skipped entirely when no `anonymousStore` is configured, or when the application opted
1116
+ * out with `clearAnonymousSessionOnLogin: false`.
1117
+ */
1118
+ async #clearAnonymousSessionAfterLogin(storeOptions) {
1119
+ if (this.#options.clearAnonymousSessionOnLogin === false) {
1120
+ return;
1121
+ }
1122
+ await this.#discardAnonymousSession(storeOptions);
1123
+ }
1124
+ /**
1125
+ * Drops the anonymous session, swallowing store failures.
1126
+ *
1127
+ * Shared by the login sites and {@link ServerClient.logout}. Both run after the user
1128
+ * session has already been written or deleted, so the outcome the caller cares about is
1129
+ * settled: a failing anonymous store must not fail a login the user completed, nor block a
1130
+ * logout redirect and leave the visitor unable to log out. When the store is the
1131
+ * cookie-backed default this cannot fail, since deleting is a `Set-Cookie` on the response.
1132
+ *
1133
+ * No-op when no `anonymousStore` is configured.
1134
+ */
1135
+ async #discardAnonymousSession(storeOptions) {
1136
+ if (!this.#anonymousClient) {
1137
+ return;
1138
+ }
1139
+ try {
1140
+ await this.#anonymousClient.logout(storeOptions);
1141
+ } catch {
1142
+ }
1143
+ }
800
1144
  /**
801
1145
  * Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
802
1146
  *
@@ -832,6 +1176,17 @@ var ServerClient = class {
832
1176
  }
833
1177
  const domain = await this.#resolveDomain(storeOptions);
834
1178
  const authClient = this.#getAuthClient(domain);
1179
+ let anonTransferToken;
1180
+ if (this.#options.anonymousStore) {
1181
+ try {
1182
+ const identifier = this.#options.anonymousSessionIdentifier || "__a0_anon";
1183
+ const stateData = await this.#options.anonymousStore.get(identifier, storeOptions);
1184
+ if (stateData?.sessionToken) {
1185
+ anonTransferToken = await authClient.anonymous.mintTransferToken(stateData.sessionToken) ?? void 0;
1186
+ }
1187
+ } catch {
1188
+ }
1189
+ }
835
1190
  const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
836
1191
  pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
837
1192
  authorizationParams: {
@@ -839,7 +1194,8 @@ var ServerClient = class {
839
1194
  redirect_uri: redirectUri,
840
1195
  scope,
841
1196
  ...resolvedOrganization ? { organization: resolvedOrganization } : {},
842
- ...options?.invitation ? { invitation: options.invitation } : {}
1197
+ ...options?.invitation ? { invitation: options.invitation } : {},
1198
+ ...anonTransferToken ? { anon_transfer_token: anonTransferToken } : {}
843
1199
  }
844
1200
  });
845
1201
  const transactionState = {
@@ -909,6 +1265,7 @@ var ServerClient = class {
909
1265
  tokenEndpointResponse.claims
910
1266
  );
911
1267
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
1268
+ await this.#clearAnonymousSessionAfterLogin(storeOptions);
912
1269
  return {
913
1270
  appState: transactionData.appState,
914
1271
  authorizationDetails: tokenEndpointResponse.authorizationDetails
@@ -1076,6 +1433,7 @@ var ServerClient = class {
1076
1433
  tokenEndpointResponse.claims
1077
1434
  );
1078
1435
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
1436
+ await this.#clearAnonymousSessionAfterLogin(storeOptions);
1079
1437
  const result = {
1080
1438
  authorizationDetails: tokenEndpointResponse.authorizationDetails
1081
1439
  };
@@ -1222,6 +1580,7 @@ var ServerClient = class {
1222
1580
  { domain }
1223
1581
  );
1224
1582
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
1583
+ await this.#clearAnonymousSessionAfterLogin(storeOptions);
1225
1584
  const result = {
1226
1585
  authorizationDetails: tokenEndpointResponse.authorizationDetails
1227
1586
  };
@@ -1272,6 +1631,7 @@ var ServerClient = class {
1272
1631
  domain
1273
1632
  });
1274
1633
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
1634
+ await this.#clearAnonymousSessionAfterLogin(storeOptions);
1275
1635
  await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
1276
1636
  return {
1277
1637
  authorizationDetails: tokenEndpointResponse.authorizationDetails
@@ -1579,6 +1939,29 @@ var ServerClient = class {
1579
1939
  }
1580
1940
  /**
1581
1941
  * Logs the user out and returns a URL to redirect the user-agent to after they log out.
1942
+ *
1943
+ * Clears the anonymous session as well, whenever an `anonymousStore` is configured. Logging
1944
+ * out means the visitor is done, so leaving a 30-day anonymous credential behind in a
1945
+ * cookie would be surprising, and `anonymous.getAccessToken()` would keep working right
1946
+ * after the user logged out. This happens even when there is no user session to clear: a
1947
+ * visitor who only ever had an anonymous session can log out. Unlike the automatic clearing
1948
+ * at login, it is not affected by `clearAnonymousSessionOnLogin`, which exists so an
1949
+ * application can finish its post-login work.
1950
+ *
1951
+ * In resolver (multi-tenant) mode the one exception is a stored user session belonging to a
1952
+ * different Auth0 domain than the request resolves to. Nothing local is cleared in that
1953
+ * case, anonymous session included, exactly as today: that state belongs to another tenant.
1954
+ *
1955
+ * The anonymous session is only ever cleared locally. The SDK does not call
1956
+ * `POST /anonymous/logout`: that endpoint exists to clear the `auth0_anon` cookie in a
1957
+ * browser, and it revokes nothing server-side. Your server never holds that cookie, so
1958
+ * calling it would achieve nothing. If the visitor's browser created an anonymous session
1959
+ * of its own (through `@auth0/auth0-spa-js`, for example), that cookie is not covered by
1960
+ * this SDK and has to be cleared from the browser.
1961
+ *
1962
+ * Anonymous access tokens already handed out stay valid until they expire; there is no
1963
+ * anonymous session to revoke them against.
1964
+ *
1582
1965
  * @param options Options used to configure the logout process.
1583
1966
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
1584
1967
  * @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.
@@ -1599,12 +1982,14 @@ var ServerClient = class {
1599
1982
  } catch {
1600
1983
  }
1601
1984
  await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
1985
+ await this.#discardAnonymousSession(storeOptions);
1602
1986
  return this.authClient.buildLogoutUrl(options);
1603
1987
  }
1604
1988
  const resolvedDomain = await this.#resolveDomain(storeOptions);
1605
1989
  const authClient = this.#getAuthClient(resolvedDomain);
1606
1990
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
1607
1991
  if (!stateData) {
1992
+ await this.#discardAnonymousSession(storeOptions);
1608
1993
  return authClient.buildLogoutUrl(options);
1609
1994
  }
1610
1995
  const sessionDomain = this.#getSessionDomain(stateData);
@@ -1615,6 +2000,7 @@ var ServerClient = class {
1615
2000
  } catch {
1616
2001
  }
1617
2002
  await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
2003
+ await this.#discardAnonymousSession(storeOptions);
1618
2004
  }
1619
2005
  return authClient.buildLogoutUrl(options);
1620
2006
  }
@@ -1646,6 +2032,7 @@ var ServerClient = class {
1646
2032
  { domain }
1647
2033
  );
1648
2034
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
2035
+ await this.#clearAnonymousSessionAfterLogin(storeOptions);
1649
2036
  const result = {
1650
2037
  authorizationDetails: tokenEndpointResponse.authorizationDetails
1651
2038
  };
@@ -1893,7 +2280,7 @@ var ServerClient = class {
1893
2280
  };
1894
2281
 
1895
2282
  // src/encryption/index.ts
1896
- var import_jose2 = require("jose");
2283
+ var import_jose3 = require("jose");
1897
2284
  var ENC = "A256CBC-HS512";
1898
2285
  var ALG = "dir";
1899
2286
  var DIGEST = "SHA-256";
@@ -1901,7 +2288,7 @@ var BIT_LENGTH = 512;
1901
2288
  var HKDF_INFO = "derived cookie encryption secret";
1902
2289
  var encoder;
1903
2290
  function isDecryptionError(e) {
1904
- return e instanceof import_jose2.errors.JWEDecryptionFailed || e instanceof import_jose2.errors.JWEInvalid;
2291
+ return e instanceof import_jose3.errors.JWEDecryptionFailed || e instanceof import_jose3.errors.JWEInvalid;
1905
2292
  }
1906
2293
  async function deriveEncryptionSecret(secret, salt, kid) {
1907
2294
  encoder ||= new TextEncoder();
@@ -1922,7 +2309,7 @@ async function deriveEncryptionSecret(secret, salt, kid) {
1922
2309
  async function _encrypt(payload, secret, salt, expiration) {
1923
2310
  const kid = crypto.randomUUID();
1924
2311
  const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
1925
- return await new import_jose2.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
2312
+ return await new import_jose3.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
1926
2313
  }
1927
2314
  async function encrypt(payload, secret, salt, expiration) {
1928
2315
  if (typeof secret === "string") {
@@ -1936,7 +2323,7 @@ async function encrypt(payload, secret, salt, expiration) {
1936
2323
  }
1937
2324
  }
1938
2325
  async function _decrypt(value, secret, salt) {
1939
- const res = await (0, import_jose2.jwtDecrypt)(
2326
+ const res = await (0, import_jose3.jwtDecrypt)(
1940
2327
  value,
1941
2328
  async (protectedHeader) => {
1942
2329
  if (!protectedHeader.kid) {
@@ -2014,7 +2401,7 @@ var AbstractTransactionStore = class extends AbstractStore {
2014
2401
  };
2015
2402
 
2016
2403
  // src/index.ts
2017
- var import_auth0_auth_js6 = require("@auth0/auth0-auth-js");
2404
+ var import_auth0_auth_js7 = require("@auth0/auth0-auth-js");
2018
2405
 
2019
2406
  // src/store/cookie-transaction-store.ts
2020
2407
  var CookieTransactionStore = class extends AbstractTransactionStore {
@@ -2210,6 +2597,88 @@ var StatelessStateStore = class extends AbstractSessionStore {
2210
2597
  }
2211
2598
  };
2212
2599
 
2600
+ // src/store/abstract-anonymous-store.ts
2601
+ var AbstractAnonymousStore = class extends AbstractStore {
2602
+ constructor(options) {
2603
+ super(options);
2604
+ }
2605
+ };
2606
+
2607
+ // src/store/stateless-anonymous-store.ts
2608
+ var DEFAULT_ANONYMOUS_SESSION_LIFETIME = 60 * 60 * 24 * 30;
2609
+ var CHUNK_SIZE = 3072;
2610
+ var StatelessAnonymousStore = class extends AbstractAnonymousStore {
2611
+ #cookieOptions;
2612
+ #cookieHandler;
2613
+ #sessionTokenLifetime;
2614
+ constructor(options, cookieHandler) {
2615
+ super(options);
2616
+ this.#cookieOptions = options.cookie;
2617
+ this.#cookieHandler = cookieHandler;
2618
+ this.#sessionTokenLifetime = options.sessionTokenLifetime ?? DEFAULT_ANONYMOUS_SESSION_LIFETIME;
2619
+ }
2620
+ async set(identifier, anonymousStateData, removeIfExists, options) {
2621
+ const expiration = this.#getExpiration(anonymousStateData);
2622
+ const maxAge = expiration - Math.floor(Date.now() / 1e3);
2623
+ if (maxAge <= 0) {
2624
+ await this.delete(identifier, options);
2625
+ return;
2626
+ }
2627
+ const cookieOpts = this.#getCookieOptions({ maxAge });
2628
+ const encryptedStateData = await this.encrypt(identifier, anonymousStateData, expiration);
2629
+ const chunkCount = Math.ceil(encryptedStateData.length / CHUNK_SIZE);
2630
+ const chunks = [...Array(chunkCount).keys()].map((i) => ({
2631
+ value: encryptedStateData.substring(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
2632
+ name: `${identifier}.${i}`
2633
+ }));
2634
+ chunks.forEach((chunk) => {
2635
+ this.#cookieHandler.setCookie(chunk.name, chunk.value, cookieOpts, options);
2636
+ });
2637
+ const existingCookieKeys = this.#getCookieKeys(identifier, options);
2638
+ existingCookieKeys.filter((key) => !chunks.some((chunk) => chunk.name === key)).forEach((key) => {
2639
+ this.#cookieHandler.deleteCookie(key, options, this.#getCookieOptions());
2640
+ });
2641
+ }
2642
+ async get(identifier, options) {
2643
+ const cookieKeys = this.#getCookieKeys(identifier, options);
2644
+ const encryptedStateData = cookieKeys.map((key) => ({
2645
+ index: parseInt(key.split(".")[1], 10),
2646
+ value: this.#cookieHandler.getCookie(key, options)
2647
+ })).sort((a, b) => a.index - b.index).map((item) => item.value).join("");
2648
+ if (encryptedStateData) {
2649
+ return await this.decrypt(identifier, encryptedStateData);
2650
+ }
2651
+ }
2652
+ async delete(identifier, options) {
2653
+ const cookieKeys = this.#getCookieKeys(identifier, options);
2654
+ for (const key of cookieKeys) {
2655
+ this.#cookieHandler.deleteCookie(key, options, this.#getCookieOptions());
2656
+ }
2657
+ }
2658
+ /**
2659
+ * Absolute expiry of the anonymous session, in Unix seconds.
2660
+ *
2661
+ * Prefers the value Auth0 reports. `sessionTokenExpiresAt` is not populated yet, because
2662
+ * `auth0-auth-js` does not surface `session_expires_in` from the token response; until it
2663
+ * does, this falls back to the configured lifetime measured from creation.
2664
+ */
2665
+ #getExpiration(anonymousStateData) {
2666
+ return anonymousStateData.sessionTokenExpiresAt ?? anonymousStateData.createdAt + this.#sessionTokenLifetime;
2667
+ }
2668
+ #getCookieKeys(identifier, options) {
2669
+ return Object.keys(this.#cookieHandler.getCookies(options)).filter((key) => key.startsWith(identifier));
2670
+ }
2671
+ #getCookieOptions(partial) {
2672
+ return {
2673
+ httpOnly: true,
2674
+ sameSite: this.#cookieOptions?.sameSite ?? "lax",
2675
+ path: this.#cookieOptions?.path ?? "/",
2676
+ secure: this.#cookieOptions?.secure ?? true,
2677
+ ...partial
2678
+ };
2679
+ }
2680
+ };
2681
+
2213
2682
  // src/mfa/index.ts
2214
2683
  var import_auth0_auth_js3 = require("@auth0/auth0-auth-js");
2215
2684
 
@@ -2218,13 +2687,20 @@ var import_auth0_auth_js4 = require("@auth0/auth0-auth-js");
2218
2687
 
2219
2688
  // src/database/index.ts
2220
2689
  var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
2690
+
2691
+ // src/anonymous/index.ts
2692
+ var import_auth0_auth_js6 = require("@auth0/auth0-auth-js");
2221
2693
  // Annotate the CommonJS export names for ESM import in node:
2222
2694
  0 && (module.exports = {
2695
+ AbstractAnonymousStore,
2223
2696
  AbstractStateStore,
2224
2697
  AbstractTransactionStore,
2698
+ AnonymousSessionError,
2699
+ AnonymousSessionExpiredError,
2225
2700
  BackchannelLogoutError,
2226
2701
  ChangePasswordError,
2227
2702
  CookieTransactionStore,
2703
+ DEFAULT_ANONYMOUS_SESSION_LIFETIME,
2228
2704
  EnterpriseConnectNotSupportedError,
2229
2705
  InvalidConfigurationError,
2230
2706
  IssuerValidationError,
@@ -2232,6 +2708,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
2232
2708
  MfaEnrollmentError,
2233
2709
  MfaListAuthenticatorsError,
2234
2710
  MfaVerifyError,
2711
+ MissingAnonymousSessionError,
2235
2712
  MissingClientAuthError,
2236
2713
  MissingRequiredArgumentError,
2237
2714
  MissingSessionError,
@@ -2242,6 +2719,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
2242
2719
  PasskeyRegisterError,
2243
2720
  PasswordlessStartError,
2244
2721
  PasswordlessVerifyError,
2722
+ ServerAnonymousClient,
2245
2723
  ServerClient,
2246
2724
  ServerDatabaseClient,
2247
2725
  ServerMfaClient,
@@ -2250,6 +2728,7 @@ var import_auth0_auth_js5 = require("@auth0/auth0-auth-js");
2250
2728
  SignUpError,
2251
2729
  StartLinkUserError,
2252
2730
  StatefulStateStore,
2731
+ StatelessAnonymousStore,
2253
2732
  StatelessStateStore,
2254
2733
  TokenExchangeError,
2255
2734
  TokenExchangeErrorCode,