@auth0/auth0-server-js 1.3.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
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  BackchannelLogoutError: () => BackchannelLogoutError,
26
26
  CookieTransactionStore: () => CookieTransactionStore,
27
27
  InvalidConfigurationError: () => InvalidConfigurationError,
28
+ IssuerValidationError: () => IssuerValidationError,
28
29
  MissingRequiredArgumentError: () => MissingRequiredArgumentError,
29
30
  MissingSessionError: () => MissingSessionError,
30
31
  MissingTransactionError: () => MissingTransactionError,
@@ -78,6 +79,13 @@ var InvalidConfigurationError = class extends Error {
78
79
  this.name = "InvalidConfigurationError";
79
80
  }
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
+ };
81
89
 
82
90
  // src/state/utils.ts
83
91
  var createUpdatedTokenSet = (audience, response) => ({
@@ -86,7 +94,7 @@ var createUpdatedTokenSet = (audience, response) => ({
86
94
  scope: response.scope,
87
95
  expiresAt: response.expiresAt
88
96
  });
89
- function updateStateData(audience, stateData, tokenEndpointResponse) {
97
+ function updateStateData(audience, stateData, tokenEndpointResponse, context) {
90
98
  if (stateData) {
91
99
  const isNewTokenSet = !stateData.tokenSets.some(
92
100
  (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope
@@ -98,7 +106,8 @@ function updateStateData(audience, stateData, tokenEndpointResponse) {
98
106
  ...stateData,
99
107
  idToken: tokenEndpointResponse.idToken ?? stateData.idToken,
100
108
  refreshToken: tokenEndpointResponse.refreshToken ?? stateData.refreshToken,
101
- tokenSets
109
+ tokenSets,
110
+ domain: context?.domain ?? stateData.domain
102
111
  };
103
112
  } else {
104
113
  const user = tokenEndpointResponse.claims;
@@ -106,9 +115,8 @@ function updateStateData(audience, stateData, tokenEndpointResponse) {
106
115
  user,
107
116
  idToken: tokenEndpointResponse.idToken,
108
117
  refreshToken: tokenEndpointResponse.refreshToken,
109
- tokenSets: [
110
- createUpdatedTokenSet(audience, tokenEndpointResponse)
111
- ],
118
+ tokenSets: [createUpdatedTokenSet(audience, tokenEndpointResponse)],
119
+ domain: context?.domain,
112
120
  internal: {
113
121
  sid: user?.sid,
114
122
  createdAt: Math.floor(Date.now() / 1e3)
@@ -153,6 +161,9 @@ var compareScopes = (scopes, requiredScopes) => {
153
161
  return requiredScopesArray.every((scope) => scopesSet.has(scope));
154
162
  };
155
163
 
164
+ // src/server-client.ts
165
+ var import_jose = require("jose");
166
+
156
167
  // src/telemetry.ts
157
168
  function getTelemetryConfig(config) {
158
169
  if (config?.enabled === false) {
@@ -161,13 +172,26 @@ function getTelemetryConfig(config) {
161
172
  return {
162
173
  enabled: true,
163
174
  name: config?.name ?? "@auth0/auth0-server-js",
164
- version: config?.version ?? "1.3.0"
175
+ version: config?.version ?? "1.4.0"
165
176
  };
166
177
  }
167
178
 
168
179
  // src/server-client.ts
169
180
  var DEFAULT_SCOPES = "openid profile email offline_access";
170
- function ensureOpenIdScope(scope) {
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) => {
171
195
  if (!scope) {
172
196
  return DEFAULT_SCOPES;
173
197
  }
@@ -176,20 +200,32 @@ function ensureOpenIdScope(scope) {
176
200
  scopes.unshift("openid");
177
201
  }
178
202
  return scopes.join(" ");
179
- }
203
+ };
180
204
  var ServerClient = class {
181
205
  #options;
182
206
  #transactionStore;
183
207
  #transactionStoreIdentifier;
184
208
  #stateStore;
185
209
  #stateStoreIdentifier;
210
+ #authClientOptions;
211
+ #staticDomain;
212
+ #authClient;
186
213
  /**
187
214
  * The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
188
215
  * Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
189
- *
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
+ *
190
221
  * Important: the methods exposed on the `authClient` instance do not handle any session or state management.
191
222
  */
192
- 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
+ }
193
229
  constructor(options) {
194
230
  this.#options = options;
195
231
  this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
@@ -202,18 +238,77 @@ var ServerClient = class {
202
238
  if (!this.#options.transactionStore) {
203
239
  throw new MissingRequiredArgumentError("transactionStore");
204
240
  }
205
- this.authClient = new import_auth0_auth_js.AuthClient({
206
- 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 = {
207
245
  clientId: this.#options.clientId,
208
246
  clientSecret: this.#options.clientSecret,
209
247
  clientAssertionSigningKey: this.#options.clientAssertionSigningKey,
210
248
  clientAssertionSigningAlg: this.#options.clientAssertionSigningAlg,
211
249
  authorizationParams: this.#options.authorizationParams,
250
+ discoveryCache: this.#options.discoveryCache,
212
251
  customFetch: this.#options.customFetch,
213
- useMtls: this.#options.useMtls,
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,
214
278
  telemetry: getTelemetryConfig(this.#options.telemetry)
215
279
  });
216
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
+ }
217
312
  /**
218
313
  * Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
219
314
  * @param options Optional options used to configure the interactive login process.
@@ -229,7 +324,9 @@ var ServerClient = class {
229
324
  throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
230
325
  }
231
326
  const scope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
232
- const { codeVerifier, authorizationUrl } = await this.authClient.buildAuthorizationUrl({
327
+ const domain = await this.#resolveDomain(storeOptions);
328
+ const authClient = this.#getAuthClient(domain);
329
+ const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
233
330
  pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
234
331
  authorizationParams: {
235
332
  ...options?.authorizationParams,
@@ -239,7 +336,8 @@ var ServerClient = class {
239
336
  });
240
337
  const transactionState = {
241
338
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
242
- codeVerifier
339
+ codeVerifier,
340
+ domain
243
341
  };
244
342
  if (options?.appState) {
245
343
  transactionState.appState = options.appState;
@@ -263,11 +361,15 @@ var ServerClient = class {
263
361
  if (!transactionData) {
264
362
  throw new MissingTransactionError();
265
363
  }
266
- 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, {
267
367
  codeVerifier: transactionData.codeVerifier
268
368
  });
269
369
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
270
- const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse);
370
+ const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
371
+ domain
372
+ });
271
373
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
272
374
  await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
273
375
  return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
@@ -289,7 +391,15 @@ var ServerClient = class {
289
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."
290
392
  );
291
393
  }
292
- 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({
293
403
  connection: options.connection,
294
404
  connectionScope: options.connectionScope,
295
405
  idToken: stateData.idToken,
@@ -297,7 +407,8 @@ var ServerClient = class {
297
407
  });
298
408
  const transactionState = {
299
409
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
300
- codeVerifier
410
+ codeVerifier,
411
+ domain
301
412
  };
302
413
  if (options?.appState) {
303
414
  transactionState.appState = options.appState;
@@ -339,14 +450,23 @@ var ServerClient = class {
339
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."
340
451
  );
341
452
  }
342
- 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({
343
462
  connection: options.connection,
344
463
  idToken: stateData.idToken,
345
464
  authorizationParams: options.authorizationParams
346
465
  });
347
466
  const transactionState = {
348
467
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
349
- codeVerifier
468
+ codeVerifier,
469
+ domain
350
470
  };
351
471
  if (options?.appState) {
352
472
  transactionState.appState = options.appState;
@@ -385,7 +505,9 @@ var ServerClient = class {
385
505
  */
386
506
  async loginBackchannel(options, storeOptions) {
387
507
  const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
388
- const tokenEndpointResponse = await this.authClient.backchannelAuthentication({
508
+ const domain = await this.#resolveDomain(storeOptions);
509
+ const authClient = this.#getAuthClient(domain);
510
+ const tokenEndpointResponse = await authClient.backchannelAuthentication({
389
511
  bindingMessage: options.bindingMessage,
390
512
  loginHint: options.loginHint,
391
513
  authorizationParams: {
@@ -397,7 +519,8 @@ var ServerClient = class {
397
519
  const stateData = updateStateData(
398
520
  this.#options.authorizationParams?.audience ?? "default",
399
521
  existingStateData,
400
- tokenEndpointResponse
522
+ tokenEndpointResponse,
523
+ { domain }
401
524
  );
402
525
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
403
526
  return {
@@ -411,16 +534,31 @@ var ServerClient = class {
411
534
  */
412
535
  async getUser(storeOptions) {
413
536
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
414
- 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;
415
547
  }
416
548
  /**
417
549
  * Retrieve the user session from the store, or undefined if no session found.
418
550
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
419
- * @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.
420
552
  */
421
553
  async getSession(storeOptions) {
422
554
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
423
555
  if (stateData) {
556
+ if (this.#isResolverMode()) {
557
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
558
+ if (!isCurrentDomain) {
559
+ return;
560
+ }
561
+ }
424
562
  const { internal, ...sessionData } = stateData;
425
563
  return sessionData;
426
564
  }
@@ -428,24 +566,29 @@ var ServerClient = class {
428
566
  /**
429
567
  * Retrieves the access token from the store, or calls Auth0 when the access token is expired and a refresh token is available in the store.
430
568
  * Also updates the store when a new token was retrieved from Auth0.
431
- *
432
- * @param options Optional options for requesting specific audience/scope.
433
569
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
434
570
  *
435
571
  * @throws {TokenByRefreshTokenError} If the refresh token was not found or there was an issue requesting the access token.
436
572
  *
437
573
  * @returns The Token Set, containing the access token, as well as additional information.
438
574
  */
439
- async getAccessToken(tokenOptionsOrStoreOptions, storeOptions) {
440
- const hasTokenOptions = (
441
- // If second arg exists, first arg must be GetAccessTokenOptions
442
- storeOptions !== void 0 || // OR if first arg has audience/scope properties
443
- tokenOptionsOrStoreOptions && typeof tokenOptionsOrStoreOptions === "object" && ("audience" in tokenOptionsOrStoreOptions || "scope" in tokenOptionsOrStoreOptions)
444
- );
445
- const [resolvedOptions, resolvedStoreOptions] = hasTokenOptions ? [tokenOptionsOrStoreOptions, storeOptions] : [void 0, tokenOptionsOrStoreOptions];
446
- const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
447
- const audience = resolvedOptions?.audience ?? this.#options.authorizationParams?.audience ?? "default";
448
- const scope = resolvedOptions?.scope ?? this.#options.authorizationParams?.scope;
575
+ async getAccessToken(storeOptions) {
576
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
577
+ const audience = this.#options.authorizationParams?.audience ?? "default";
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
+ }
449
592
  const tokenSet = stateData?.tokenSets.find(
450
593
  (tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
451
594
  );
@@ -457,14 +600,15 @@ var ServerClient = class {
457
600
  "The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
458
601
  );
459
602
  }
460
- const tokenByRefreshTokenOptions = {
461
- refreshToken: stateData.refreshToken,
462
- ...hasTokenOptions && { audience, scope }
463
- };
464
- const tokenEndpointResponse = await this.authClient.getTokenByRefreshToken(tokenByRefreshTokenOptions);
465
- const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
466
- const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse);
467
- await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, resolvedStoreOptions);
603
+ const domainForSession = sessionDomain;
604
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenByRefreshToken({
605
+ refreshToken: stateData.refreshToken
606
+ });
607
+ const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
608
+ const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
609
+ domain: domainForSession
610
+ });
611
+ await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
468
612
  return {
469
613
  accessToken: tokenEndpointResponse.accessToken,
470
614
  scope: tokenEndpointResponse.scope,
@@ -489,6 +633,19 @@ var ServerClient = class {
489
633
  */
490
634
  async getAccessTokenForConnection(options, storeOptions) {
491
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
+ }
492
649
  const connectionTokenSet = stateData?.connectionTokenSets?.find(
493
650
  (tokenSet) => tokenSet.connection === options.connection
494
651
  );
@@ -500,12 +657,20 @@ var ServerClient = class {
500
657
  "A refresh token was not found but is required to be able to retrieve an access token for a connection."
501
658
  );
502
659
  }
503
- const tokenEndpointResponse = await this.authClient.getTokenForConnection({
660
+ const domainForSession = sessionDomain;
661
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenForConnection({
504
662
  connection: options.connection,
505
663
  loginHint: options.loginHint,
506
664
  refreshToken: stateData.refreshToken
507
665
  });
508
- const updatedStateData = updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse);
666
+ const updatedStateData = updateStateDataForConnectionTokenSet(
667
+ options,
668
+ {
669
+ ...stateData,
670
+ domain: stateData.domain ?? domainForSession
671
+ },
672
+ tokenEndpointResponse
673
+ );
509
674
  await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
510
675
  return {
511
676
  accessToken: tokenEndpointResponse.accessToken,
@@ -522,8 +687,21 @@ var ServerClient = class {
522
687
  * @returns {URL}
523
688
  */
524
689
  async logout(options, storeOptions) {
525
- await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
526
- 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);
527
705
  }
528
706
  /**
529
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.
@@ -537,13 +715,28 @@ var ServerClient = class {
537
715
  if (!logoutToken) {
538
716
  throw new BackchannelLogoutError("Missing Logout Token");
539
717
  }
540
- const logoutTokenClaims = await this.authClient.verifyLogoutToken({ logoutToken });
541
- 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);
542
735
  }
543
736
  };
544
737
 
545
738
  // src/encryption/index.ts
546
- var import_jose = require("jose");
739
+ var import_jose2 = require("jose");
547
740
  var ENC = "A256CBC-HS512";
548
741
  var ALG = "dir";
549
742
  var DIGEST = "SHA-256";
@@ -551,7 +744,7 @@ var BIT_LENGTH = 512;
551
744
  var HKDF_INFO = "derived cookie encryption secret";
552
745
  var encoder;
553
746
  function isDecryptionError(e) {
554
- return e instanceof import_jose.errors.JWEDecryptionFailed || e instanceof import_jose.errors.JWEInvalid;
747
+ return e instanceof import_jose2.errors.JWEDecryptionFailed || e instanceof import_jose2.errors.JWEInvalid;
555
748
  }
556
749
  async function deriveEncryptionSecret(secret, salt, kid) {
557
750
  encoder ||= new TextEncoder();
@@ -572,7 +765,7 @@ async function deriveEncryptionSecret(secret, salt, kid) {
572
765
  async function _encrypt(payload, secret, salt, expiration) {
573
766
  const kid = crypto.randomUUID();
574
767
  const encryptionSecret = await deriveEncryptionSecret(secret, salt, kid);
575
- 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);
576
769
  }
577
770
  async function encrypt(payload, secret, salt, expiration) {
578
771
  if (typeof secret === "string") {
@@ -586,7 +779,7 @@ async function encrypt(payload, secret, salt, expiration) {
586
779
  }
587
780
  }
588
781
  async function _decrypt(value, secret, salt) {
589
- const res = await (0, import_jose.jwtDecrypt)(
782
+ const res = await (0, import_jose2.jwtDecrypt)(
590
783
  value,
591
784
  async (protectedHeader) => {
592
785
  if (!protectedHeader.kid) {
@@ -863,6 +1056,7 @@ var StatelessStateStore = class extends AbstractSessionStore {
863
1056
  BackchannelLogoutError,
864
1057
  CookieTransactionStore,
865
1058
  InvalidConfigurationError,
1059
+ IssuerValidationError,
866
1060
  MissingRequiredArgumentError,
867
1061
  MissingSessionError,
868
1062
  MissingTransactionError,