@auth0/auth0-server-js 1.7.0 → 1.8.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.d.cts CHANGED
@@ -92,6 +92,15 @@ interface SessionData {
92
92
  tokenSets: TokenSet[];
93
93
  connectionTokenSets?: ConnectionTokenSet[];
94
94
  domain?: string;
95
+ /**
96
+ * IPSIE SL1 `session_expiry` ceiling for this session, as an absolute Unix timestamp in
97
+ * seconds. Present only when the user logged in through an enterprise connection configured
98
+ * with `id_token_session_expiry_supported: true`. When set, the SDK treats the session as
99
+ * expired once this time is reached (minus a small leeway) and forces re-authentication.
100
+ * Absent for database/social logins, connections without the option, and sessions created
101
+ * before this feature — those behave unchanged.
102
+ */
103
+ sessionExpiresAt?: number;
95
104
  [key: string]: unknown;
96
105
  }
97
106
  interface TransactionData {
@@ -619,6 +628,7 @@ declare class ServerClient<TStoreOptions = unknown> {
619
628
  *
620
629
  * @throws {MissingTransactionError} When no transaction was found.
621
630
  * @throws {TokenByCodeError} If there was an issue requesting the access token.
631
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
622
632
  *
623
633
  * @returns A promise resolving to an object, containing the original appState (if present) and the authorizationDetails (when RAR was used).
624
634
  */
@@ -633,6 +643,7 @@ declare class ServerClient<TStoreOptions = unknown> {
633
643
  *
634
644
  * @throws {MissingSessionError} If there is no active session.
635
645
  * @throws {BuildLinkUserUrlError} If there was an issue when building the Authorization URL.
646
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
636
647
  *
637
648
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
638
649
  */
@@ -658,6 +669,7 @@ declare class ServerClient<TStoreOptions = unknown> {
658
669
  *
659
670
  * @throws {MissingSessionError} If there is no active session.
660
671
  * @throws {BuildUnlinkUserUrlError} If there was an issue when building the User Unlinking URL.
672
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
661
673
  *
662
674
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
663
675
  */
@@ -685,6 +697,7 @@ declare class ServerClient<TStoreOptions = unknown> {
685
697
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
686
698
  *
687
699
  * @throws {BackchannelAuthenticationError} If there was an issue when doing backchannel authentication.
700
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
688
701
  *
689
702
  * @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
690
703
  */
@@ -954,8 +967,9 @@ declare abstract class AbstractSessionStore<TStoreOptions> extends AbstractState
954
967
  constructor(options: SessionConfiguration & EncryptedStoreOptions);
955
968
  /**
956
969
  * calculateMaxAge calculates the max age of the session based on createdAt and the rolling and absolute durations.
970
+ * When sessionExpiresAt is provided, caps the maxAge to not exceed the time until that ceiling.
957
971
  */
958
- protected calculateMaxAge(createdAt: number): number;
972
+ protected calculateMaxAge(createdAt: number, sessionExpiresAt?: number): number;
959
973
  }
960
974
 
961
975
  interface StatefulStateStoreOptions<TStoreOptions> extends EncryptedStoreOptions {
@@ -1030,5 +1044,13 @@ declare class IssuerValidationError extends Error {
1030
1044
  code: string;
1031
1045
  constructor(message: string);
1032
1046
  }
1047
+ /**
1048
+ * Error thrown when the session has passed its upstream IdP-asserted
1049
+ * `session_expiry` ceiling (IPSIE SL1). The user must re-authenticate.
1050
+ */
1051
+ declare class SessionExpiredError extends Error {
1052
+ code: string;
1053
+ constructor(message?: string);
1054
+ }
1033
1055
 
1034
- export { type AbstractDataStore, AbstractStateStore, AbstractTransactionStore, type AccessTokenForConnectionOptions, type AuthorizationParameters, BackchannelLogoutError, type CompletePasswordlessEmailOptions, type CompletePasswordlessOptions, type CompletePasswordlessResult, type CompletePasswordlessSmsOptions, type ConnectionTokenSet, type CookieHandler, type CookieSerializeOptions, CookieTransactionStore, type CustomTokenExchangeOptions, type DomainResolver, type EncryptedStoreOptions, type GetAccessTokenOptions, type InternalStateData, InvalidConfigurationError, IssuerValidationError, type LoginBackchannelOptions, type LoginBackchannelResult, type LoginWithCustomTokenExchangeOptions, type LoginWithCustomTokenExchangeResult, type LogoutOptions, type LogoutTokenClaims, type MfaVerifyResponse, MissingRequiredArgumentError, MissingSessionError, MissingTransactionError, type PasskeyGetTokenResult, ServerClient, type ServerClientOptions, ServerMfaClient, ServerPasskeyClient, type SessionConfiguration, type SessionCookieOptions, type SessionData, type SessionStore, type StartInteractiveLoginOptions, StartLinkUserError, type StartLinkUserOptions, type StartPasswordlessEmailCodeOptions, type StartPasswordlessEmailLinkOptions, type StartPasswordlessOptions, type StartPasswordlessSmsOptions, type StartUnlinkUserOptions, type StateData, type StateStore, StatefulStateStore, type StatefulStateStoreOptions, StatelessStateStore, type TokenSet, type TransactionData, type TransactionStore, type UserClaims };
1056
+ export { type AbstractDataStore, AbstractStateStore, AbstractTransactionStore, type AccessTokenForConnectionOptions, type AuthorizationParameters, BackchannelLogoutError, type CompletePasswordlessEmailOptions, type CompletePasswordlessOptions, type CompletePasswordlessResult, type CompletePasswordlessSmsOptions, type ConnectionTokenSet, type CookieHandler, type CookieSerializeOptions, CookieTransactionStore, type CustomTokenExchangeOptions, type DomainResolver, type EncryptedStoreOptions, type GetAccessTokenOptions, type InternalStateData, InvalidConfigurationError, IssuerValidationError, type LoginBackchannelOptions, type LoginBackchannelResult, type LoginWithCustomTokenExchangeOptions, type LoginWithCustomTokenExchangeResult, type LogoutOptions, type LogoutTokenClaims, type MfaVerifyResponse, MissingRequiredArgumentError, MissingSessionError, MissingTransactionError, type PasskeyGetTokenResult, ServerClient, type ServerClientOptions, ServerMfaClient, ServerPasskeyClient, type SessionConfiguration, type SessionCookieOptions, type SessionData, SessionExpiredError, type SessionStore, type StartInteractiveLoginOptions, StartLinkUserError, type StartLinkUserOptions, type StartPasswordlessEmailCodeOptions, type StartPasswordlessEmailLinkOptions, type StartPasswordlessOptions, type StartPasswordlessSmsOptions, type StartUnlinkUserOptions, type StateData, type StateStore, StatefulStateStore, type StatefulStateStoreOptions, StatelessStateStore, type TokenSet, type TransactionData, type TransactionStore, type UserClaims };
package/dist/index.d.ts CHANGED
@@ -92,6 +92,15 @@ interface SessionData {
92
92
  tokenSets: TokenSet[];
93
93
  connectionTokenSets?: ConnectionTokenSet[];
94
94
  domain?: string;
95
+ /**
96
+ * IPSIE SL1 `session_expiry` ceiling for this session, as an absolute Unix timestamp in
97
+ * seconds. Present only when the user logged in through an enterprise connection configured
98
+ * with `id_token_session_expiry_supported: true`. When set, the SDK treats the session as
99
+ * expired once this time is reached (minus a small leeway) and forces re-authentication.
100
+ * Absent for database/social logins, connections without the option, and sessions created
101
+ * before this feature — those behave unchanged.
102
+ */
103
+ sessionExpiresAt?: number;
95
104
  [key: string]: unknown;
96
105
  }
97
106
  interface TransactionData {
@@ -619,6 +628,7 @@ declare class ServerClient<TStoreOptions = unknown> {
619
628
  *
620
629
  * @throws {MissingTransactionError} When no transaction was found.
621
630
  * @throws {TokenByCodeError} If there was an issue requesting the access token.
631
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
622
632
  *
623
633
  * @returns A promise resolving to an object, containing the original appState (if present) and the authorizationDetails (when RAR was used).
624
634
  */
@@ -633,6 +643,7 @@ declare class ServerClient<TStoreOptions = unknown> {
633
643
  *
634
644
  * @throws {MissingSessionError} If there is no active session.
635
645
  * @throws {BuildLinkUserUrlError} If there was an issue when building the Authorization URL.
646
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
636
647
  *
637
648
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
638
649
  */
@@ -658,6 +669,7 @@ declare class ServerClient<TStoreOptions = unknown> {
658
669
  *
659
670
  * @throws {MissingSessionError} If there is no active session.
660
671
  * @throws {BuildUnlinkUserUrlError} If there was an issue when building the User Unlinking URL.
672
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
661
673
  *
662
674
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
663
675
  */
@@ -685,6 +697,7 @@ declare class ServerClient<TStoreOptions = unknown> {
685
697
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
686
698
  *
687
699
  * @throws {BackchannelAuthenticationError} If there was an issue when doing backchannel authentication.
700
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
688
701
  *
689
702
  * @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
690
703
  */
@@ -954,8 +967,9 @@ declare abstract class AbstractSessionStore<TStoreOptions> extends AbstractState
954
967
  constructor(options: SessionConfiguration & EncryptedStoreOptions);
955
968
  /**
956
969
  * calculateMaxAge calculates the max age of the session based on createdAt and the rolling and absolute durations.
970
+ * When sessionExpiresAt is provided, caps the maxAge to not exceed the time until that ceiling.
957
971
  */
958
- protected calculateMaxAge(createdAt: number): number;
972
+ protected calculateMaxAge(createdAt: number, sessionExpiresAt?: number): number;
959
973
  }
960
974
 
961
975
  interface StatefulStateStoreOptions<TStoreOptions> extends EncryptedStoreOptions {
@@ -1030,5 +1044,13 @@ declare class IssuerValidationError extends Error {
1030
1044
  code: string;
1031
1045
  constructor(message: string);
1032
1046
  }
1047
+ /**
1048
+ * Error thrown when the session has passed its upstream IdP-asserted
1049
+ * `session_expiry` ceiling (IPSIE SL1). The user must re-authenticate.
1050
+ */
1051
+ declare class SessionExpiredError extends Error {
1052
+ code: string;
1053
+ constructor(message?: string);
1054
+ }
1033
1055
 
1034
- export { type AbstractDataStore, AbstractStateStore, AbstractTransactionStore, type AccessTokenForConnectionOptions, type AuthorizationParameters, BackchannelLogoutError, type CompletePasswordlessEmailOptions, type CompletePasswordlessOptions, type CompletePasswordlessResult, type CompletePasswordlessSmsOptions, type ConnectionTokenSet, type CookieHandler, type CookieSerializeOptions, CookieTransactionStore, type CustomTokenExchangeOptions, type DomainResolver, type EncryptedStoreOptions, type GetAccessTokenOptions, type InternalStateData, InvalidConfigurationError, IssuerValidationError, type LoginBackchannelOptions, type LoginBackchannelResult, type LoginWithCustomTokenExchangeOptions, type LoginWithCustomTokenExchangeResult, type LogoutOptions, type LogoutTokenClaims, type MfaVerifyResponse, MissingRequiredArgumentError, MissingSessionError, MissingTransactionError, type PasskeyGetTokenResult, ServerClient, type ServerClientOptions, ServerMfaClient, ServerPasskeyClient, type SessionConfiguration, type SessionCookieOptions, type SessionData, type SessionStore, type StartInteractiveLoginOptions, StartLinkUserError, type StartLinkUserOptions, type StartPasswordlessEmailCodeOptions, type StartPasswordlessEmailLinkOptions, type StartPasswordlessOptions, type StartPasswordlessSmsOptions, type StartUnlinkUserOptions, type StateData, type StateStore, StatefulStateStore, type StatefulStateStoreOptions, StatelessStateStore, type TokenSet, type TransactionData, type TransactionStore, type UserClaims };
1056
+ export { type AbstractDataStore, AbstractStateStore, AbstractTransactionStore, type AccessTokenForConnectionOptions, type AuthorizationParameters, BackchannelLogoutError, type CompletePasswordlessEmailOptions, type CompletePasswordlessOptions, type CompletePasswordlessResult, type CompletePasswordlessSmsOptions, type ConnectionTokenSet, type CookieHandler, type CookieSerializeOptions, CookieTransactionStore, type CustomTokenExchangeOptions, type DomainResolver, type EncryptedStoreOptions, type GetAccessTokenOptions, type InternalStateData, InvalidConfigurationError, IssuerValidationError, type LoginBackchannelOptions, type LoginBackchannelResult, type LoginWithCustomTokenExchangeOptions, type LoginWithCustomTokenExchangeResult, type LogoutOptions, type LogoutTokenClaims, type MfaVerifyResponse, MissingRequiredArgumentError, MissingSessionError, MissingTransactionError, type PasskeyGetTokenResult, ServerClient, type ServerClientOptions, ServerMfaClient, ServerPasskeyClient, type SessionConfiguration, type SessionCookieOptions, type SessionData, SessionExpiredError, type SessionStore, type StartInteractiveLoginOptions, StartLinkUserError, type StartLinkUserOptions, type StartPasswordlessEmailCodeOptions, type StartPasswordlessEmailLinkOptions, type StartPasswordlessOptions, type StartPasswordlessSmsOptions, type StartUnlinkUserOptions, type StateData, type StateStore, StatefulStateStore, type StatefulStateStoreOptions, StatelessStateStore, type TokenSet, type TransactionData, type TransactionStore, type UserClaims };
package/dist/index.js CHANGED
@@ -48,14 +48,55 @@ var IssuerValidationError = class extends Error {
48
48
  this.name = "IssuerValidationError";
49
49
  }
50
50
  };
51
+ var SessionExpiredError = class extends Error {
52
+ code = "session_expired";
53
+ constructor(message) {
54
+ super(
55
+ message ?? "The session has expired because the upstream identity provider session ceiling was reached. The user needs to re-authenticate."
56
+ );
57
+ this.name = "SessionExpiredError";
58
+ }
59
+ };
51
60
 
52
61
  // src/state/utils.ts
62
+ var SESSION_EXPIRY_LEEWAY = 30;
63
+ var MAX_PLAUSIBLE_UNIX_SECONDS = 1e10;
64
+ function isPlausibleUnixSeconds(value) {
65
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value < MAX_PLAUSIBLE_UNIX_SECONDS;
66
+ }
67
+ function extractSessionExpiry(claims) {
68
+ const value = claims?.session_expiry;
69
+ return isPlausibleUnixSeconds(value) ? value : void 0;
70
+ }
71
+ function isSessionExpiryReached(sessionExpiresAt, nowSeconds) {
72
+ if (sessionExpiresAt === void 0) {
73
+ return false;
74
+ }
75
+ const now = nowSeconds ?? Math.floor(Date.now() / 1e3);
76
+ return now >= sessionExpiresAt - SESSION_EXPIRY_LEEWAY;
77
+ }
53
78
  var createUpdatedTokenSet = (audience, response) => ({
54
79
  audience,
55
80
  accessToken: response.accessToken,
56
81
  scope: response.scope,
57
82
  expiresAt: response.expiresAt
58
83
  });
84
+ function isSessionExpiryInPast(sessionExpiresAt, issuedAt) {
85
+ if (sessionExpiresAt === void 0) {
86
+ return false;
87
+ }
88
+ const reference = isPlausibleUnixSeconds(issuedAt) ? issuedAt : Math.floor(Date.now() / 1e3);
89
+ return sessionExpiresAt <= reference + SESSION_EXPIRY_LEEWAY;
90
+ }
91
+ function applySessionExpiryAtLogin(stateData, claims) {
92
+ const sessionExpiresAt = extractSessionExpiry(claims);
93
+ if (isSessionExpiryInPast(sessionExpiresAt, claims?.iat)) {
94
+ throw new SessionExpiredError(
95
+ "The upstream identity provider session_expiry is at or before the issued-at time; refusing to create an already-expired session."
96
+ );
97
+ }
98
+ return { ...stateData, sessionExpiresAt };
99
+ }
59
100
  function updateStateData(audience, stateData, tokenEndpointResponse, context) {
60
101
  if (stateData && tokenEndpointResponse.claims) {
61
102
  const newSub = tokenEndpointResponse.claims.sub;
@@ -163,7 +204,7 @@ function getTelemetryConfig(config) {
163
204
  return {
164
205
  enabled: true,
165
206
  name: config?.name ?? "@auth0/auth0-server-js",
166
- version: config?.version ?? "1.7.0"
207
+ version: config?.version ?? "1.8.0"
167
208
  };
168
209
  }
169
210
 
@@ -225,9 +266,12 @@ var ServerMfaClient = class {
225
266
  this.#options.stateStoreIdentifier,
226
267
  storeOptions
227
268
  );
228
- const updatedStateData = updateStateData(audience, existingStateData, tokenResponse, {
229
- domain: this.#options.domain
230
- });
269
+ const updatedStateData = applySessionExpiryAtLogin(
270
+ updateStateData(audience, existingStateData, tokenResponse, {
271
+ domain: this.#options.domain
272
+ }),
273
+ tokenResponse.claims
274
+ );
231
275
  await this.#options.stateStore.set(
232
276
  this.#options.stateStoreIdentifier,
233
277
  updatedStateData,
@@ -553,6 +597,7 @@ var ServerClient = class {
553
597
  *
554
598
  * @throws {MissingTransactionError} When no transaction was found.
555
599
  * @throws {TokenByCodeError} If there was an issue requesting the access token.
600
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
556
601
  *
557
602
  * @returns A promise resolving to an object, containing the original appState (if present) and the authorizationDetails (when RAR was used).
558
603
  */
@@ -567,12 +612,15 @@ var ServerClient = class {
567
612
  // TransactionData.codeVerifier is optional only to accommodate magic-link transactions.
568
613
  codeVerifier: transactionData.codeVerifier
569
614
  });
615
+ await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
570
616
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
571
- const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
572
- domain
573
- });
617
+ const stateData = applySessionExpiryAtLogin(
618
+ updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
619
+ domain
620
+ }),
621
+ tokenEndpointResponse.claims
622
+ );
574
623
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
575
- await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
576
624
  return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
577
625
  }
578
626
  /**
@@ -582,6 +630,7 @@ var ServerClient = class {
582
630
  *
583
631
  * @throws {MissingSessionError} If there is no active session.
584
632
  * @throws {BuildLinkUserUrlError} If there was an issue when building the Authorization URL.
633
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
585
634
  *
586
635
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
587
636
  */
@@ -598,6 +647,10 @@ var ServerClient = class {
598
647
  throw new MissingSessionError("Session domain does not match the current domain.");
599
648
  }
600
649
  }
650
+ if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
651
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
652
+ throw new SessionExpiredError();
653
+ }
601
654
  const domain = this.#getSessionDomain(stateData);
602
655
  const authClient = this.#getAuthClient(domain);
603
656
  const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
@@ -641,6 +694,7 @@ var ServerClient = class {
641
694
  *
642
695
  * @throws {MissingSessionError} If there is no active session.
643
696
  * @throws {BuildUnlinkUserUrlError} If there was an issue when building the User Unlinking URL.
697
+ * @throws {SessionExpiredError} When the session's `session_expiry` ceiling has been reached; the session is cleared and re-authentication is required.
644
698
  *
645
699
  * @returns A promise resolving to a URL object, representing the URL to redirect the user-agent to to request authorization at Auth0.
646
700
  */
@@ -657,6 +711,10 @@ var ServerClient = class {
657
711
  throw new MissingSessionError("Session domain does not match the current domain.");
658
712
  }
659
713
  }
714
+ if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
715
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
716
+ throw new SessionExpiredError();
717
+ }
660
718
  const domain = this.#getSessionDomain(stateData);
661
719
  const authClient = this.#getAuthClient(domain);
662
720
  const { unlinkUserUrl, codeVerifier } = await authClient.buildUnlinkUserUrl({
@@ -701,6 +759,7 @@ var ServerClient = class {
701
759
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
702
760
  *
703
761
  * @throws {BackchannelAuthenticationError} If there was an issue when doing backchannel authentication.
762
+ * @throws {SessionExpiredError} When the ID token's `session_expiry` is already in the past at login (the session is born expired); nothing is persisted.
704
763
  *
705
764
  * @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
706
765
  */
@@ -717,11 +776,11 @@ var ServerClient = class {
717
776
  }
718
777
  });
719
778
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
720
- const stateData = updateStateData(
721
- this.#options.authorizationParams?.audience ?? "default",
722
- existingStateData,
723
- tokenEndpointResponse,
724
- { domain }
779
+ const stateData = applySessionExpiryAtLogin(
780
+ updateStateData(this.#options.authorizationParams?.audience ?? "default", existingStateData, tokenEndpointResponse, {
781
+ domain
782
+ }),
783
+ tokenEndpointResponse.claims
725
784
  );
726
785
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
727
786
  return {
@@ -916,6 +975,10 @@ var ServerClient = class {
916
975
  return;
917
976
  }
918
977
  }
978
+ if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
979
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
980
+ return;
981
+ }
919
982
  return stateData.user;
920
983
  }
921
984
  /**
@@ -932,6 +995,10 @@ var ServerClient = class {
932
995
  return;
933
996
  }
934
997
  }
998
+ if (isSessionExpiryReached(stateData.sessionExpiresAt)) {
999
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
1000
+ return;
1001
+ }
935
1002
  const { internal, ...sessionData } = stateData;
936
1003
  return sessionData;
937
1004
  }
@@ -948,6 +1015,7 @@ var ServerClient = class {
948
1015
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
949
1016
  *
950
1017
  * @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`.
1018
+ * @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.
951
1019
  *
952
1020
  * @returns The Token Set, containing the access token, as well as additional information.
953
1021
  */
@@ -975,6 +1043,10 @@ var ServerClient = class {
975
1043
  throw new MissingSessionError("Session domain does not match the current domain.");
976
1044
  }
977
1045
  }
1046
+ if (stateData && isSessionExpiryReached(stateData.sessionExpiresAt)) {
1047
+ await this.#stateStore.delete(this.#stateStoreIdentifier, resolvedStoreOptions);
1048
+ throw new SessionExpiredError();
1049
+ }
978
1050
  const tokenSet = stateData?.tokenSets.find(
979
1051
  (tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
980
1052
  );
@@ -1349,14 +1421,14 @@ var AbstractSessionStore = class extends AbstractStateStore {
1349
1421
  }
1350
1422
  /**
1351
1423
  * calculateMaxAge calculates the max age of the session based on createdAt and the rolling and absolute durations.
1424
+ * When sessionExpiresAt is provided, caps the maxAge to not exceed the time until that ceiling.
1352
1425
  */
1353
- calculateMaxAge(createdAt) {
1354
- if (!this.#rolling) {
1355
- return this.#absoluteDuration;
1356
- }
1426
+ calculateMaxAge(createdAt, sessionExpiresAt) {
1357
1427
  const now = Date.now() / 1e3 | 0;
1358
- const expiresAt = Math.min(now + this.#inactivityDuration, createdAt + this.#absoluteDuration);
1359
- const maxAge = expiresAt - now;
1428
+ let maxAge = this.#rolling ? Math.min(now + this.#inactivityDuration, createdAt + this.#absoluteDuration) - now : this.#absoluteDuration;
1429
+ if (sessionExpiresAt !== void 0) {
1430
+ maxAge = Math.min(maxAge, sessionExpiresAt - now);
1431
+ }
1360
1432
  return maxAge > 0 ? maxAge : 0;
1361
1433
  }
1362
1434
  };
@@ -1384,7 +1456,7 @@ var StatefulStateStore = class extends AbstractSessionStore {
1384
1456
  sessionId = generateId();
1385
1457
  }
1386
1458
  sessionId ??= generateId();
1387
- const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
1459
+ const maxAge = this.calculateMaxAge(stateData.internal.createdAt, stateData.sessionExpiresAt);
1388
1460
  const cookieOpts = this.#getCookieOptions({
1389
1461
  maxAge
1390
1462
  });
@@ -1449,7 +1521,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
1449
1521
  this.#cookieHandler = cookieHandler;
1450
1522
  }
1451
1523
  async set(identifier, stateData, removeIfExists, options) {
1452
- const maxAge = this.calculateMaxAge(stateData.internal.createdAt);
1524
+ const maxAge = this.calculateMaxAge(stateData.internal.createdAt, stateData.sessionExpiresAt);
1453
1525
  const cookieOpts = this.#getCookieOptions({
1454
1526
  maxAge
1455
1527
  });
@@ -1543,6 +1615,7 @@ export {
1543
1615
  ServerClient,
1544
1616
  ServerMfaClient,
1545
1617
  ServerPasskeyClient,
1618
+ SessionExpiredError,
1546
1619
  StartLinkUserError,
1547
1620
  StatefulStateStore,
1548
1621
  StatelessStateStore,