@auth0/auth0-server-js 1.2.0 → 1.4.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 CHANGED
@@ -49,7 +49,8 @@ const auth0 = new ServerClient<StoreOptions>({
49
49
  ```
50
50
 
51
51
  The `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, and `AUTH0_CLIENT_SECRET` can be obtained from the [Auth0 Dashboard](https://manage.auth0.com) once you've created an application. **This application must be a `Regular Web Application`**.
52
- The `AUTH0_REDIRECT_URI` is needed to tell Auth0 what URL to redirect back to after successfull authentication, e.g. `http://localhost:3000/auth/callback`. (note, your application needs to handle this endpoint and call the SDK's `completeInteractiveLogin(url: string)` to finish the authentication process. See below for more information)
52
+ The `AUTH0_REDIRECT_URI` is needed to tell Auth0 what URL to redirect back to after successful authentication, e.g. `http://localhost:3000/auth/callback`. Your application needs to handle this endpoint and call the SDK's `completeInteractiveLogin(url: string)` to finish the authentication process. See below for more information.
53
+
53
54
 
54
55
  ### 3. Configuring the Store
55
56
 
@@ -200,6 +201,10 @@ const auth0 = new ServerClient<StoreOptions>({
200
201
  });
201
202
  ```
202
203
 
204
+ The `redirect_uri` must be an absolute URL.
205
+
206
+ If you need to compute an absolute redirect URI per request (for example, in multi-domain deployments), pass `authorizationParams.redirect_uri` to `startInteractiveLogin` to override the configured default.
207
+
203
208
  > [!IMPORTANT]
204
209
  > You will need to register the `AUTH0_REDIRECT_URI` in your Auth0 Application as an **Allowed Callback URLs** via the [Auth0 Dashboard](https://manage.auth0.com):
205
210
 
package/dist/index.cjs CHANGED
@@ -24,6 +24,8 @@ __export(index_exports, {
24
24
  AbstractTransactionStore: () => AbstractTransactionStore,
25
25
  BackchannelLogoutError: () => BackchannelLogoutError,
26
26
  CookieTransactionStore: () => CookieTransactionStore,
27
+ InvalidConfigurationError: () => InvalidConfigurationError,
28
+ IssuerValidationError: () => IssuerValidationError,
27
29
  MissingRequiredArgumentError: () => MissingRequiredArgumentError,
28
30
  MissingSessionError: () => MissingSessionError,
29
31
  MissingTransactionError: () => MissingTransactionError,
@@ -70,6 +72,20 @@ var MissingSessionError = class extends Error {
70
72
  this.name = "MissingSessionError";
71
73
  }
72
74
  };
75
+ var InvalidConfigurationError = class extends Error {
76
+ code = "invalid_configuration_error";
77
+ constructor(message) {
78
+ super(message);
79
+ this.name = "InvalidConfigurationError";
80
+ }
81
+ };
82
+ var IssuerValidationError = class extends Error {
83
+ code = "issuer_validation_error";
84
+ constructor(message) {
85
+ super(message);
86
+ this.name = "IssuerValidationError";
87
+ }
88
+ };
73
89
 
74
90
  // src/state/utils.ts
75
91
  var createUpdatedTokenSet = (audience, response) => ({
@@ -78,19 +94,20 @@ var createUpdatedTokenSet = (audience, response) => ({
78
94
  scope: response.scope,
79
95
  expiresAt: response.expiresAt
80
96
  });
81
- function updateStateData(audience, stateDate, tokenEndpointResponse) {
82
- if (stateDate) {
83
- const isNewTokenSet = !stateDate.tokenSets.some(
97
+ function updateStateData(audience, stateData, tokenEndpointResponse, context) {
98
+ if (stateData) {
99
+ const isNewTokenSet = !stateData.tokenSets.some(
84
100
  (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope
85
101
  );
86
- const tokenSets = isNewTokenSet ? [...stateDate.tokenSets, createUpdatedTokenSet(audience, tokenEndpointResponse)] : stateDate.tokenSets.map(
102
+ const tokenSets = isNewTokenSet ? [...stateData.tokenSets, createUpdatedTokenSet(audience, tokenEndpointResponse)] : stateData.tokenSets.map(
87
103
  (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope ? createUpdatedTokenSet(audience, tokenEndpointResponse) : tokenSet
88
104
  );
89
105
  return {
90
- ...stateDate,
91
- idToken: tokenEndpointResponse.idToken,
92
- refreshToken: tokenEndpointResponse.refreshToken ?? stateDate.refreshToken,
93
- tokenSets
106
+ ...stateData,
107
+ idToken: tokenEndpointResponse.idToken ?? stateData.idToken,
108
+ refreshToken: tokenEndpointResponse.refreshToken ?? stateData.refreshToken,
109
+ tokenSets,
110
+ domain: context?.domain ?? stateData.domain
94
111
  };
95
112
  } else {
96
113
  const user = tokenEndpointResponse.claims;
@@ -98,9 +115,8 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
98
115
  user,
99
116
  idToken: tokenEndpointResponse.idToken,
100
117
  refreshToken: tokenEndpointResponse.refreshToken,
101
- tokenSets: [
102
- createUpdatedTokenSet(audience, tokenEndpointResponse)
103
- ],
118
+ tokenSets: [createUpdatedTokenSet(audience, tokenEndpointResponse)],
119
+ domain: context?.domain,
104
120
  internal: {
105
121
  sid: user?.sid,
106
122
  createdAt: Math.floor(Date.now() / 1e3)
@@ -108,9 +124,9 @@ function updateStateData(audience, stateDate, tokenEndpointResponse) {
108
124
  };
109
125
  }
110
126
  }
111
- function updateStateDataForConnectionTokenSet(options, stateDate, tokenEndpointResponse) {
112
- stateDate.connectionTokenSets = stateDate.connectionTokenSets || [];
113
- const isNewTokenSet = !stateDate.connectionTokenSets.some(
127
+ function updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse) {
128
+ stateData.connectionTokenSets = stateData.connectionTokenSets || [];
129
+ const isNewTokenSet = !stateData.connectionTokenSets.some(
114
130
  (tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint)
115
131
  );
116
132
  const connectionTokenSet = {
@@ -120,11 +136,11 @@ function updateStateDataForConnectionTokenSet(options, stateDate, tokenEndpointR
120
136
  scope: tokenEndpointResponse.scope,
121
137
  expiresAt: tokenEndpointResponse.expiresAt
122
138
  };
123
- const connectionTokenSets = isNewTokenSet ? [...stateDate.connectionTokenSets, connectionTokenSet] : stateDate.connectionTokenSets.map(
139
+ const connectionTokenSets = isNewTokenSet ? [...stateData.connectionTokenSets, connectionTokenSet] : stateData.connectionTokenSets.map(
124
140
  (tokenSet) => tokenSet.connection === options.connection && (!options.loginHint || tokenSet.loginHint === options.loginHint) ? connectionTokenSet : tokenSet
125
141
  );
126
142
  return {
127
- ...stateDate,
143
+ ...stateData,
128
144
  connectionTokenSets
129
145
  };
130
146
  }
@@ -146,19 +162,70 @@ var compareScopes = (scopes, requiredScopes) => {
146
162
  };
147
163
 
148
164
  // src/server-client.ts
165
+ var import_jose = require("jose");
166
+
167
+ // src/telemetry.ts
168
+ function getTelemetryConfig(config) {
169
+ if (config?.enabled === false) {
170
+ return config;
171
+ }
172
+ return {
173
+ enabled: true,
174
+ name: config?.name ?? "@auth0/auth0-server-js",
175
+ version: config?.version ?? "1.4.0"
176
+ };
177
+ }
178
+
179
+ // src/server-client.ts
180
+ var DEFAULT_SCOPES = "openid profile email offline_access";
181
+ var normalizeDomain = (value) => {
182
+ const trimmed = value.trim();
183
+ const parsed = trimmed.startsWith("http") ? new URL(trimmed) : new URL(`https://${trimmed}`);
184
+ return parsed.host.toLowerCase();
185
+ };
186
+ var decodeIssuer = (token) => {
187
+ try {
188
+ const { iss } = (0, import_jose.decodeJwt)(token);
189
+ return typeof iss === "string" ? iss : void 0;
190
+ } catch {
191
+ return void 0;
192
+ }
193
+ };
194
+ var ensureOpenIdScope = (scope) => {
195
+ if (!scope) {
196
+ return DEFAULT_SCOPES;
197
+ }
198
+ const scopes = scope.split(" ");
199
+ if (!scopes.includes("openid")) {
200
+ scopes.unshift("openid");
201
+ }
202
+ return scopes.join(" ");
203
+ };
149
204
  var ServerClient = class {
150
205
  #options;
151
206
  #transactionStore;
152
207
  #transactionStoreIdentifier;
153
208
  #stateStore;
154
209
  #stateStoreIdentifier;
210
+ #authClientOptions;
211
+ #staticDomain;
212
+ #authClient;
155
213
  /**
156
214
  * The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
157
215
  * Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
158
- *
216
+ *
217
+ * This property can only be used when `domain` is configured as a static string.
218
+ * In resolver mode (`domain` as a function), the SDK resolves the domain per request,
219
+ * so use `ServerClient` methods instead.
220
+ *
159
221
  * Important: the methods exposed on the `authClient` instance do not handle any session or state management.
160
222
  */
161
- authClient;
223
+ get authClient() {
224
+ if (!this.#authClient) {
225
+ throw new InvalidConfigurationError("authClient is only available when using a static domain configuration.");
226
+ }
227
+ return this.#authClient;
228
+ }
162
229
  constructor(options) {
163
230
  this.#options = options;
164
231
  this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
@@ -171,17 +238,77 @@ var ServerClient = class {
171
238
  if (!this.#options.transactionStore) {
172
239
  throw new MissingRequiredArgumentError("transactionStore");
173
240
  }
174
- this.authClient = new import_auth0_auth_js.AuthClient({
175
- domain: this.#options.domain,
241
+ if (typeof this.#options.domain !== "string" && typeof this.#options.domain !== "function") {
242
+ throw new InvalidConfigurationError("domain must be a string or resolver function");
243
+ }
244
+ this.#authClientOptions = {
176
245
  clientId: this.#options.clientId,
177
246
  clientSecret: this.#options.clientSecret,
178
247
  clientAssertionSigningKey: this.#options.clientAssertionSigningKey,
179
248
  clientAssertionSigningAlg: this.#options.clientAssertionSigningAlg,
180
249
  authorizationParams: this.#options.authorizationParams,
250
+ discoveryCache: this.#options.discoveryCache,
181
251
  customFetch: this.#options.customFetch,
182
252
  useMtls: this.#options.useMtls
253
+ };
254
+ if (typeof this.#options.domain === "string") {
255
+ const domain = normalizeDomain(this.#options.domain);
256
+ this.#staticDomain = domain;
257
+ this.#authClient = new import_auth0_auth_js.AuthClient({
258
+ domain,
259
+ ...this.#authClientOptions,
260
+ telemetry: getTelemetryConfig(this.#options.telemetry)
261
+ });
262
+ }
263
+ }
264
+ async #resolveDomain(storeOptions) {
265
+ if (typeof this.#options.domain === "function") {
266
+ const resolved = await this.#options.domain(storeOptions);
267
+ if (typeof resolved !== "string" || resolved.trim().length === 0) {
268
+ throw new InvalidConfigurationError("domainResolver returned no domain");
269
+ }
270
+ return normalizeDomain(resolved);
271
+ }
272
+ return normalizeDomain(this.#options.domain);
273
+ }
274
+ #createAuthClient(domain) {
275
+ return new import_auth0_auth_js.AuthClient({
276
+ domain,
277
+ ...this.#authClientOptions,
278
+ telemetry: getTelemetryConfig(this.#options.telemetry)
183
279
  });
184
280
  }
281
+ #getAuthClient(domain) {
282
+ const normalizedDomain = normalizeDomain(domain);
283
+ if (this.#authClient && this.#staticDomain === normalizedDomain) {
284
+ return this.#authClient;
285
+ }
286
+ return this.#createAuthClient(normalizedDomain);
287
+ }
288
+ #getSessionDomain(stateData) {
289
+ if (stateData.domain) {
290
+ return normalizeDomain(stateData.domain);
291
+ }
292
+ if (this.#staticDomain) {
293
+ return this.#staticDomain;
294
+ }
295
+ const issuerFromClaims = stateData.user?.iss;
296
+ if (typeof issuerFromClaims === "string" && issuerFromClaims.trim().length > 0) {
297
+ return normalizeDomain(issuerFromClaims);
298
+ }
299
+ return;
300
+ }
301
+ #isResolverMode() {
302
+ return typeof this.#options.domain === "function";
303
+ }
304
+ async #isSessionForCurrentDomain(stateData, storeOptions) {
305
+ const sessionDomain = this.#getSessionDomain(stateData);
306
+ if (!sessionDomain) {
307
+ return false;
308
+ }
309
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
310
+ return sessionDomain === resolvedDomain;
311
+ }
185
312
  /**
186
313
  * Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
187
314
  * @param options Optional options used to configure the interactive login process.
@@ -196,16 +323,21 @@ var ServerClient = class {
196
323
  if (!redirectUri) {
197
324
  throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
198
325
  }
199
- const { codeVerifier, authorizationUrl } = await this.authClient.buildAuthorizationUrl({
326
+ const scope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
327
+ const domain = await this.#resolveDomain(storeOptions);
328
+ const authClient = this.#getAuthClient(domain);
329
+ const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
200
330
  pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
201
331
  authorizationParams: {
202
332
  ...options?.authorizationParams,
203
- redirect_uri: redirectUri
333
+ redirect_uri: redirectUri,
334
+ scope
204
335
  }
205
336
  });
206
337
  const transactionState = {
207
338
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
208
- codeVerifier
339
+ codeVerifier,
340
+ domain
209
341
  };
210
342
  if (options?.appState) {
211
343
  transactionState.appState = options.appState;
@@ -229,11 +361,15 @@ var ServerClient = class {
229
361
  if (!transactionData) {
230
362
  throw new MissingTransactionError();
231
363
  }
232
- const tokenEndpointResponse = await this.authClient.getTokenByCode(url, {
364
+ const domain = transactionData.domain ?? await this.#resolveDomain(storeOptions);
365
+ const authClient = this.#getAuthClient(domain);
366
+ const tokenEndpointResponse = await authClient.getTokenByCode(url, {
233
367
  codeVerifier: transactionData.codeVerifier
234
368
  });
235
369
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
236
- const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse);
370
+ const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
371
+ domain
372
+ });
237
373
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
238
374
  await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
239
375
  return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
@@ -255,7 +391,15 @@ var ServerClient = class {
255
391
  "Unable to start the user linking process without a logged in user. Ensure to login using the SDK before starting the user linking process."
256
392
  );
257
393
  }
258
- const { linkUserUrl, codeVerifier } = await this.authClient.buildLinkUserUrl({
394
+ if (this.#isResolverMode()) {
395
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
396
+ if (!isCurrentDomain) {
397
+ throw new MissingSessionError("Session domain does not match the current domain.");
398
+ }
399
+ }
400
+ const domain = this.#getSessionDomain(stateData);
401
+ const authClient = this.#getAuthClient(domain);
402
+ const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
259
403
  connection: options.connection,
260
404
  connectionScope: options.connectionScope,
261
405
  idToken: stateData.idToken,
@@ -263,7 +407,8 @@ var ServerClient = class {
263
407
  });
264
408
  const transactionState = {
265
409
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
266
- codeVerifier
410
+ codeVerifier,
411
+ domain
267
412
  };
268
413
  if (options?.appState) {
269
414
  transactionState.appState = options.appState;
@@ -305,14 +450,23 @@ var ServerClient = class {
305
450
  "Unable to start the user unlinking process without a logged in user. Ensure to login using the SDK before starting the user unlinking process."
306
451
  );
307
452
  }
308
- const { unlinkUserUrl, codeVerifier } = await this.authClient.buildUnlinkUserUrl({
453
+ if (this.#isResolverMode()) {
454
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
455
+ if (!isCurrentDomain) {
456
+ throw new MissingSessionError("Session domain does not match the current domain.");
457
+ }
458
+ }
459
+ const domain = this.#getSessionDomain(stateData);
460
+ const authClient = this.#getAuthClient(domain);
461
+ const { unlinkUserUrl, codeVerifier } = await authClient.buildUnlinkUserUrl({
309
462
  connection: options.connection,
310
463
  idToken: stateData.idToken,
311
464
  authorizationParams: options.authorizationParams
312
465
  });
313
466
  const transactionState = {
314
467
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
315
- codeVerifier
468
+ codeVerifier,
469
+ domain
316
470
  };
317
471
  if (options?.appState) {
318
472
  transactionState.appState = options.appState;
@@ -350,16 +504,23 @@ var ServerClient = class {
350
504
  * @returns A promise resolving to an object, containing the authorizationDetails (when RAR was used).
351
505
  */
352
506
  async loginBackchannel(options, storeOptions) {
353
- const tokenEndpointResponse = await this.authClient.backchannelAuthentication({
507
+ const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
508
+ const domain = await this.#resolveDomain(storeOptions);
509
+ const authClient = this.#getAuthClient(domain);
510
+ const tokenEndpointResponse = await authClient.backchannelAuthentication({
354
511
  bindingMessage: options.bindingMessage,
355
512
  loginHint: options.loginHint,
356
- authorizationParams: options.authorizationParams
513
+ authorizationParams: {
514
+ ...options.authorizationParams,
515
+ scope
516
+ }
357
517
  });
358
518
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
359
519
  const stateData = updateStateData(
360
520
  this.#options.authorizationParams?.audience ?? "default",
361
521
  existingStateData,
362
- tokenEndpointResponse
522
+ tokenEndpointResponse,
523
+ { domain }
363
524
  );
364
525
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
365
526
  return {
@@ -373,16 +534,31 @@ var ServerClient = class {
373
534
  */
374
535
  async getUser(storeOptions) {
375
536
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
376
- return stateData?.user;
537
+ if (!stateData) {
538
+ return;
539
+ }
540
+ if (this.#isResolverMode()) {
541
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
542
+ if (!isCurrentDomain) {
543
+ return;
544
+ }
545
+ }
546
+ return stateData.user;
377
547
  }
378
548
  /**
379
549
  * Retrieve the user session from the store, or undefined if no session found.
380
550
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
381
- * @returns The sessionm or undefined if no session found in the store.
551
+ * @returns The session or undefined if no session found in the store.
382
552
  */
383
553
  async getSession(storeOptions) {
384
554
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
385
555
  if (stateData) {
556
+ if (this.#isResolverMode()) {
557
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
558
+ if (!isCurrentDomain) {
559
+ return;
560
+ }
561
+ }
386
562
  const { internal, ...sessionData } = stateData;
387
563
  return sessionData;
388
564
  }
@@ -400,6 +576,19 @@ var ServerClient = class {
400
576
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
401
577
  const audience = this.#options.authorizationParams?.audience ?? "default";
402
578
  const scope = this.#options.authorizationParams?.scope;
579
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
580
+ if (this.#isResolverMode()) {
581
+ if (!stateData) {
582
+ throw new MissingSessionError("Unable to retrieve access token without a logged in user.");
583
+ }
584
+ if (!sessionDomain) {
585
+ throw new MissingSessionError("Session domain does not match the current domain.");
586
+ }
587
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
588
+ if (sessionDomain !== resolvedDomain) {
589
+ throw new MissingSessionError("Session domain does not match the current domain.");
590
+ }
591
+ }
403
592
  const tokenSet = stateData?.tokenSets.find(
404
593
  (tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
405
594
  );
@@ -411,11 +600,14 @@ var ServerClient = class {
411
600
  "The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
412
601
  );
413
602
  }
414
- const tokenEndpointResponse = await this.authClient.getTokenByRefreshToken({
603
+ const domainForSession = sessionDomain;
604
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenByRefreshToken({
415
605
  refreshToken: stateData.refreshToken
416
606
  });
417
607
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
418
- const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse);
608
+ const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
609
+ domain: domainForSession
610
+ });
419
611
  await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
420
612
  return {
421
613
  accessToken: tokenEndpointResponse.accessToken,
@@ -441,6 +633,19 @@ var ServerClient = class {
441
633
  */
442
634
  async getAccessTokenForConnection(options, storeOptions) {
443
635
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
636
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
637
+ if (this.#isResolverMode()) {
638
+ if (!stateData) {
639
+ throw new MissingSessionError("Unable to retrieve an access token for a connection without a logged in user.");
640
+ }
641
+ if (!sessionDomain) {
642
+ throw new MissingSessionError("Session domain does not match the current domain.");
643
+ }
644
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
645
+ if (sessionDomain !== resolvedDomain) {
646
+ throw new MissingSessionError("Session domain does not match the current domain.");
647
+ }
648
+ }
444
649
  const connectionTokenSet = stateData?.connectionTokenSets?.find(
445
650
  (tokenSet) => tokenSet.connection === options.connection
446
651
  );
@@ -452,12 +657,20 @@ var ServerClient = class {
452
657
  "A refresh token was not found but is required to be able to retrieve an access token for a connection."
453
658
  );
454
659
  }
455
- const tokenEndpointResponse = await this.authClient.getTokenForConnection({
660
+ const domainForSession = sessionDomain;
661
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenForConnection({
456
662
  connection: options.connection,
457
663
  loginHint: options.loginHint,
458
664
  refreshToken: stateData.refreshToken
459
665
  });
460
- const updatedStateData = updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse);
666
+ const updatedStateData = updateStateDataForConnectionTokenSet(
667
+ options,
668
+ {
669
+ ...stateData,
670
+ domain: stateData.domain ?? domainForSession
671
+ },
672
+ tokenEndpointResponse
673
+ );
461
674
  await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
462
675
  return {
463
676
  accessToken: tokenEndpointResponse.accessToken,
@@ -474,8 +687,21 @@ var ServerClient = class {
474
687
  * @returns {URL}
475
688
  */
476
689
  async logout(options, storeOptions) {
477
- await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
478
- return this.authClient.buildLogoutUrl(options);
690
+ if (!this.#isResolverMode()) {
691
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
692
+ return this.authClient.buildLogoutUrl(options);
693
+ }
694
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
695
+ const authClient = this.#getAuthClient(resolvedDomain);
696
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
697
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : void 0;
698
+ if (!stateData) {
699
+ return authClient.buildLogoutUrl(options);
700
+ }
701
+ if (sessionDomain && sessionDomain === resolvedDomain) {
702
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
703
+ }
704
+ return authClient.buildLogoutUrl(options);
479
705
  }
480
706
  /**
481
707
  * Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
@@ -489,19 +715,37 @@ var ServerClient = class {
489
715
  if (!logoutToken) {
490
716
  throw new BackchannelLogoutError("Missing Logout Token");
491
717
  }
492
- const logoutTokenClaims = await this.authClient.verifyLogoutToken({ logoutToken });
493
- await this.#stateStore.deleteByLogoutToken(logoutTokenClaims, storeOptions);
718
+ if (!this.#isResolverMode()) {
719
+ const logoutTokenClaims2 = await this.authClient.verifyLogoutToken({ logoutToken });
720
+ await this.#stateStore.deleteByLogoutToken(logoutTokenClaims2, storeOptions);
721
+ return;
722
+ }
723
+ const issuer = decodeIssuer(logoutToken);
724
+ if (!issuer) {
725
+ throw new BackchannelLogoutError("Logout token is missing an issuer");
726
+ }
727
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
728
+ const domain = normalizeDomain(issuer);
729
+ if (domain !== resolvedDomain) {
730
+ throw new BackchannelLogoutError("Logout token issuer does not match the resolved domain");
731
+ }
732
+ const authClient = this.#getAuthClient(domain);
733
+ const logoutTokenClaims = await authClient.verifyLogoutToken({ logoutToken });
734
+ await this.#stateStore.deleteByLogoutToken({ ...logoutTokenClaims, iss: issuer }, storeOptions);
494
735
  }
495
736
  };
496
737
 
497
738
  // src/encryption/index.ts
498
- var import_jose = require("jose");
739
+ var import_jose2 = require("jose");
499
740
  var ENC = "A256CBC-HS512";
500
741
  var ALG = "dir";
501
742
  var DIGEST = "SHA-256";
502
743
  var BIT_LENGTH = 512;
503
744
  var HKDF_INFO = "derived cookie encryption secret";
504
745
  var encoder;
746
+ function isDecryptionError(e) {
747
+ return e instanceof import_jose2.errors.JWEDecryptionFailed || e instanceof import_jose2.errors.JWEInvalid;
748
+ }
505
749
  async function deriveEncryptionSecret(secret, salt, kid) {
506
750
  encoder ||= new TextEncoder();
507
751
  const key = await crypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
@@ -518,13 +762,24 @@ async function deriveEncryptionSecret(secret, salt, kid) {
518
762
  )
519
763
  );
520
764
  }
521
- async function encrypt(payload, secret, salt, expiration) {
765
+ async function _encrypt(payload, secret, salt, expiration) {
522
766
  const kid = crypto.randomUUID();
523
767
  const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
524
- return await new import_jose.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
768
+ return await new import_jose2.EncryptJWT(payload).setProtectedHeader({ enc: ENC, alg: ALG, kid }).setExpirationTime(expiration).encrypt(encryptionSecret);
525
769
  }
526
- async function decrypt(value, secret, salt) {
527
- const res = await (0, import_jose.jwtDecrypt)(
770
+ async function encrypt(payload, secret, salt, expiration) {
771
+ if (typeof secret === "string") {
772
+ return await _encrypt(payload, secret, salt, expiration);
773
+ } else {
774
+ const [newSecret] = secret;
775
+ if (!newSecret) {
776
+ throw new InvalidConfigurationError("At least one secret must be provided");
777
+ }
778
+ return await _encrypt(payload, newSecret, salt, expiration);
779
+ }
780
+ }
781
+ async function _decrypt(value, secret, salt) {
782
+ const res = await (0, import_jose2.jwtDecrypt)(
528
783
  value,
529
784
  async (protectedHeader) => {
530
785
  if (!protectedHeader.kid) {
@@ -536,6 +791,35 @@ async function decrypt(value, secret, salt) {
536
791
  );
537
792
  return res.payload;
538
793
  }
794
+ async function decrypt(value, secret, salt) {
795
+ if (typeof secret === "string") {
796
+ return await _decrypt(value, secret, salt);
797
+ } else {
798
+ const [newSecret, ...oldSecrets] = secret;
799
+ if (!newSecret) {
800
+ throw new InvalidConfigurationError("At least one secret must be provided");
801
+ }
802
+ let firstError;
803
+ try {
804
+ return await _decrypt(value, newSecret, salt);
805
+ } catch (err) {
806
+ firstError = err;
807
+ if (!isDecryptionError(err)) {
808
+ throw firstError;
809
+ }
810
+ for (const oldSecret of oldSecrets) {
811
+ try {
812
+ return await _decrypt(value, oldSecret, salt);
813
+ } catch (fallbackErr) {
814
+ if (!isDecryptionError(fallbackErr)) {
815
+ throw fallbackErr;
816
+ }
817
+ }
818
+ }
819
+ throw firstError;
820
+ }
821
+ }
822
+ }
539
823
 
540
824
  // src/store/abstract-store.ts
541
825
  var AbstractStore = class {
@@ -547,7 +831,14 @@ var AbstractStore = class {
547
831
  return await encrypt(stateData, this.options.secret, identifier, expiration);
548
832
  }
549
833
  async decrypt(identifier, encryptedStateData) {
550
- return await decrypt(encryptedStateData, this.options.secret, identifier);
834
+ try {
835
+ return await decrypt(encryptedStateData, this.options.secret, identifier);
836
+ } catch (e) {
837
+ if (isDecryptionError(e)) {
838
+ return;
839
+ }
840
+ throw e;
841
+ }
551
842
  }
552
843
  };
553
844
 
@@ -674,7 +965,9 @@ var StatefulStateStore = class extends AbstractSessionStore {
674
965
  const cookieValue = this.#cookieHandler.getCookie(identifier, options);
675
966
  if (cookieValue) {
676
967
  const sessionCookie = await this.decrypt(identifier, cookieValue);
677
- return sessionCookie.id;
968
+ if (sessionCookie) {
969
+ return sessionCookie.id;
970
+ }
678
971
  }
679
972
  }
680
973
  deleteByLogoutToken(claims, options) {
@@ -762,6 +1055,8 @@ var StatelessStateStore = class extends AbstractSessionStore {
762
1055
  AbstractTransactionStore,
763
1056
  BackchannelLogoutError,
764
1057
  CookieTransactionStore,
1058
+ InvalidConfigurationError,
1059
+ IssuerValidationError,
765
1060
  MissingRequiredArgumentError,
766
1061
  MissingSessionError,
767
1062
  MissingTransactionError,