@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/dist/index.js CHANGED
@@ -41,6 +41,13 @@ var InvalidConfigurationError = class extends Error {
41
41
  this.name = "InvalidConfigurationError";
42
42
  }
43
43
  };
44
+ var IssuerValidationError = class extends Error {
45
+ code = "issuer_validation_error";
46
+ constructor(message) {
47
+ super(message);
48
+ this.name = "IssuerValidationError";
49
+ }
50
+ };
44
51
 
45
52
  // src/state/utils.ts
46
53
  var createUpdatedTokenSet = (audience, response) => ({
@@ -49,7 +56,7 @@ var createUpdatedTokenSet = (audience, response) => ({
49
56
  scope: response.scope,
50
57
  expiresAt: response.expiresAt
51
58
  });
52
- function updateStateData(audience, stateData, tokenEndpointResponse) {
59
+ function updateStateData(audience, stateData, tokenEndpointResponse, context) {
53
60
  if (stateData) {
54
61
  const isNewTokenSet = !stateData.tokenSets.some(
55
62
  (tokenSet) => tokenSet.audience === audience && tokenSet.scope === tokenEndpointResponse.scope
@@ -61,7 +68,8 @@ function updateStateData(audience, stateData, tokenEndpointResponse) {
61
68
  ...stateData,
62
69
  idToken: tokenEndpointResponse.idToken ?? stateData.idToken,
63
70
  refreshToken: tokenEndpointResponse.refreshToken ?? stateData.refreshToken,
64
- tokenSets
71
+ tokenSets,
72
+ domain: context?.domain ?? stateData.domain
65
73
  };
66
74
  } else {
67
75
  const user = tokenEndpointResponse.claims;
@@ -69,9 +77,8 @@ function updateStateData(audience, stateData, tokenEndpointResponse) {
69
77
  user,
70
78
  idToken: tokenEndpointResponse.idToken,
71
79
  refreshToken: tokenEndpointResponse.refreshToken,
72
- tokenSets: [
73
- createUpdatedTokenSet(audience, tokenEndpointResponse)
74
- ],
80
+ tokenSets: [createUpdatedTokenSet(audience, tokenEndpointResponse)],
81
+ domain: context?.domain,
75
82
  internal: {
76
83
  sid: user?.sid,
77
84
  createdAt: Math.floor(Date.now() / 1e3)
@@ -120,6 +127,9 @@ var compareScopes = (scopes, requiredScopes) => {
120
127
  return requiredScopesArray.every((scope) => scopesSet.has(scope));
121
128
  };
122
129
 
130
+ // src/server-client.ts
131
+ import { decodeJwt } from "jose";
132
+
123
133
  // src/telemetry.ts
124
134
  function getTelemetryConfig(config) {
125
135
  if (config?.enabled === false) {
@@ -128,13 +138,26 @@ function getTelemetryConfig(config) {
128
138
  return {
129
139
  enabled: true,
130
140
  name: config?.name ?? "@auth0/auth0-server-js",
131
- version: config?.version ?? "1.3.0"
141
+ version: config?.version ?? "1.4.0"
132
142
  };
133
143
  }
134
144
 
135
145
  // src/server-client.ts
136
146
  var DEFAULT_SCOPES = "openid profile email offline_access";
137
- function ensureOpenIdScope(scope) {
147
+ var normalizeDomain = (value) => {
148
+ const trimmed = value.trim();
149
+ const parsed = trimmed.startsWith("http") ? new URL(trimmed) : new URL(`https://${trimmed}`);
150
+ return parsed.host.toLowerCase();
151
+ };
152
+ var decodeIssuer = (token) => {
153
+ try {
154
+ const { iss } = decodeJwt(token);
155
+ return typeof iss === "string" ? iss : void 0;
156
+ } catch {
157
+ return void 0;
158
+ }
159
+ };
160
+ var ensureOpenIdScope = (scope) => {
138
161
  if (!scope) {
139
162
  return DEFAULT_SCOPES;
140
163
  }
@@ -143,20 +166,32 @@ function ensureOpenIdScope(scope) {
143
166
  scopes.unshift("openid");
144
167
  }
145
168
  return scopes.join(" ");
146
- }
169
+ };
147
170
  var ServerClient = class {
148
171
  #options;
149
172
  #transactionStore;
150
173
  #transactionStoreIdentifier;
151
174
  #stateStore;
152
175
  #stateStoreIdentifier;
176
+ #authClientOptions;
177
+ #staticDomain;
178
+ #authClient;
153
179
  /**
154
180
  * The underlying `authClient` instance that can be used to interact with the Auth0 Authentication API.
155
181
  * Generally, you should prefer to use the higher-level methods exposed on the `ServerClient` instance.
156
- *
182
+ *
183
+ * This property can only be used when `domain` is configured as a static string.
184
+ * In resolver mode (`domain` as a function), the SDK resolves the domain per request,
185
+ * so use `ServerClient` methods instead.
186
+ *
157
187
  * Important: the methods exposed on the `authClient` instance do not handle any session or state management.
158
188
  */
159
- authClient;
189
+ get authClient() {
190
+ if (!this.#authClient) {
191
+ throw new InvalidConfigurationError("authClient is only available when using a static domain configuration.");
192
+ }
193
+ return this.#authClient;
194
+ }
160
195
  constructor(options) {
161
196
  this.#options = options;
162
197
  this.#stateStoreIdentifier = this.#options.stateIdentifier || "__a0_session";
@@ -169,18 +204,77 @@ var ServerClient = class {
169
204
  if (!this.#options.transactionStore) {
170
205
  throw new MissingRequiredArgumentError("transactionStore");
171
206
  }
172
- this.authClient = new AuthClient({
173
- domain: this.#options.domain,
207
+ if (typeof this.#options.domain !== "string" && typeof this.#options.domain !== "function") {
208
+ throw new InvalidConfigurationError("domain must be a string or resolver function");
209
+ }
210
+ this.#authClientOptions = {
174
211
  clientId: this.#options.clientId,
175
212
  clientSecret: this.#options.clientSecret,
176
213
  clientAssertionSigningKey: this.#options.clientAssertionSigningKey,
177
214
  clientAssertionSigningAlg: this.#options.clientAssertionSigningAlg,
178
215
  authorizationParams: this.#options.authorizationParams,
216
+ discoveryCache: this.#options.discoveryCache,
179
217
  customFetch: this.#options.customFetch,
180
- useMtls: this.#options.useMtls,
218
+ useMtls: this.#options.useMtls
219
+ };
220
+ if (typeof this.#options.domain === "string") {
221
+ const domain = normalizeDomain(this.#options.domain);
222
+ this.#staticDomain = domain;
223
+ this.#authClient = new AuthClient({
224
+ domain,
225
+ ...this.#authClientOptions,
226
+ telemetry: getTelemetryConfig(this.#options.telemetry)
227
+ });
228
+ }
229
+ }
230
+ async #resolveDomain(storeOptions) {
231
+ if (typeof this.#options.domain === "function") {
232
+ const resolved = await this.#options.domain(storeOptions);
233
+ if (typeof resolved !== "string" || resolved.trim().length === 0) {
234
+ throw new InvalidConfigurationError("domainResolver returned no domain");
235
+ }
236
+ return normalizeDomain(resolved);
237
+ }
238
+ return normalizeDomain(this.#options.domain);
239
+ }
240
+ #createAuthClient(domain) {
241
+ return new AuthClient({
242
+ domain,
243
+ ...this.#authClientOptions,
181
244
  telemetry: getTelemetryConfig(this.#options.telemetry)
182
245
  });
183
246
  }
247
+ #getAuthClient(domain) {
248
+ const normalizedDomain = normalizeDomain(domain);
249
+ if (this.#authClient && this.#staticDomain === normalizedDomain) {
250
+ return this.#authClient;
251
+ }
252
+ return this.#createAuthClient(normalizedDomain);
253
+ }
254
+ #getSessionDomain(stateData) {
255
+ if (stateData.domain) {
256
+ return normalizeDomain(stateData.domain);
257
+ }
258
+ if (this.#staticDomain) {
259
+ return this.#staticDomain;
260
+ }
261
+ const issuerFromClaims = stateData.user?.iss;
262
+ if (typeof issuerFromClaims === "string" && issuerFromClaims.trim().length > 0) {
263
+ return normalizeDomain(issuerFromClaims);
264
+ }
265
+ return;
266
+ }
267
+ #isResolverMode() {
268
+ return typeof this.#options.domain === "function";
269
+ }
270
+ async #isSessionForCurrentDomain(stateData, storeOptions) {
271
+ const sessionDomain = this.#getSessionDomain(stateData);
272
+ if (!sessionDomain) {
273
+ return false;
274
+ }
275
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
276
+ return sessionDomain === resolvedDomain;
277
+ }
184
278
  /**
185
279
  * Starts the interactive login process, and returns a URL to redirect the user-agent to to request authorization at Auth0.
186
280
  * @param options Optional options used to configure the interactive login process.
@@ -196,7 +290,9 @@ var ServerClient = class {
196
290
  throw new MissingRequiredArgumentError("authorizationParams.redirect_uri");
197
291
  }
198
292
  const scope = ensureOpenIdScope(options?.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
199
- const { codeVerifier, authorizationUrl } = await this.authClient.buildAuthorizationUrl({
293
+ const domain = await this.#resolveDomain(storeOptions);
294
+ const authClient = this.#getAuthClient(domain);
295
+ const { codeVerifier, authorizationUrl } = await authClient.buildAuthorizationUrl({
200
296
  pushedAuthorizationRequests: options?.pushedAuthorizationRequests,
201
297
  authorizationParams: {
202
298
  ...options?.authorizationParams,
@@ -206,7 +302,8 @@ var ServerClient = class {
206
302
  });
207
303
  const transactionState = {
208
304
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
209
- codeVerifier
305
+ codeVerifier,
306
+ domain
210
307
  };
211
308
  if (options?.appState) {
212
309
  transactionState.appState = options.appState;
@@ -230,11 +327,15 @@ var ServerClient = class {
230
327
  if (!transactionData) {
231
328
  throw new MissingTransactionError();
232
329
  }
233
- const tokenEndpointResponse = await this.authClient.getTokenByCode(url, {
330
+ const domain = transactionData.domain ?? await this.#resolveDomain(storeOptions);
331
+ const authClient = this.#getAuthClient(domain);
332
+ const tokenEndpointResponse = await authClient.getTokenByCode(url, {
234
333
  codeVerifier: transactionData.codeVerifier
235
334
  });
236
335
  const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
237
- const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse);
336
+ const stateData = updateStateData(transactionData.audience ?? "default", existingStateData, tokenEndpointResponse, {
337
+ domain
338
+ });
238
339
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
239
340
  await this.#transactionStore.delete(this.#transactionStoreIdentifier, storeOptions);
240
341
  return { appState: transactionData.appState, authorizationDetails: tokenEndpointResponse.authorizationDetails };
@@ -256,7 +357,15 @@ var ServerClient = class {
256
357
  "Unable to start the user linking process without a logged in user. Ensure to login using the SDK before starting the user linking process."
257
358
  );
258
359
  }
259
- const { linkUserUrl, codeVerifier } = await this.authClient.buildLinkUserUrl({
360
+ if (this.#isResolverMode()) {
361
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
362
+ if (!isCurrentDomain) {
363
+ throw new MissingSessionError("Session domain does not match the current domain.");
364
+ }
365
+ }
366
+ const domain = this.#getSessionDomain(stateData);
367
+ const authClient = this.#getAuthClient(domain);
368
+ const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
260
369
  connection: options.connection,
261
370
  connectionScope: options.connectionScope,
262
371
  idToken: stateData.idToken,
@@ -264,7 +373,8 @@ var ServerClient = class {
264
373
  });
265
374
  const transactionState = {
266
375
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
267
- codeVerifier
376
+ codeVerifier,
377
+ domain
268
378
  };
269
379
  if (options?.appState) {
270
380
  transactionState.appState = options.appState;
@@ -306,14 +416,23 @@ var ServerClient = class {
306
416
  "Unable to start the user unlinking process without a logged in user. Ensure to login using the SDK before starting the user unlinking process."
307
417
  );
308
418
  }
309
- const { unlinkUserUrl, codeVerifier } = await this.authClient.buildUnlinkUserUrl({
419
+ if (this.#isResolverMode()) {
420
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
421
+ if (!isCurrentDomain) {
422
+ throw new MissingSessionError("Session domain does not match the current domain.");
423
+ }
424
+ }
425
+ const domain = this.#getSessionDomain(stateData);
426
+ const authClient = this.#getAuthClient(domain);
427
+ const { unlinkUserUrl, codeVerifier } = await authClient.buildUnlinkUserUrl({
310
428
  connection: options.connection,
311
429
  idToken: stateData.idToken,
312
430
  authorizationParams: options.authorizationParams
313
431
  });
314
432
  const transactionState = {
315
433
  audience: options?.authorizationParams?.audience ?? this.#options.authorizationParams?.audience,
316
- codeVerifier
434
+ codeVerifier,
435
+ domain
317
436
  };
318
437
  if (options?.appState) {
319
438
  transactionState.appState = options.appState;
@@ -352,7 +471,9 @@ var ServerClient = class {
352
471
  */
353
472
  async loginBackchannel(options, storeOptions) {
354
473
  const scope = ensureOpenIdScope(options.authorizationParams?.scope ?? this.#options.authorizationParams?.scope);
355
- const tokenEndpointResponse = await this.authClient.backchannelAuthentication({
474
+ const domain = await this.#resolveDomain(storeOptions);
475
+ const authClient = this.#getAuthClient(domain);
476
+ const tokenEndpointResponse = await authClient.backchannelAuthentication({
356
477
  bindingMessage: options.bindingMessage,
357
478
  loginHint: options.loginHint,
358
479
  authorizationParams: {
@@ -364,7 +485,8 @@ var ServerClient = class {
364
485
  const stateData = updateStateData(
365
486
  this.#options.authorizationParams?.audience ?? "default",
366
487
  existingStateData,
367
- tokenEndpointResponse
488
+ tokenEndpointResponse,
489
+ { domain }
368
490
  );
369
491
  await this.#stateStore.set(this.#stateStoreIdentifier, stateData, true, storeOptions);
370
492
  return {
@@ -378,16 +500,31 @@ var ServerClient = class {
378
500
  */
379
501
  async getUser(storeOptions) {
380
502
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
381
- return stateData?.user;
503
+ if (!stateData) {
504
+ return;
505
+ }
506
+ if (this.#isResolverMode()) {
507
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
508
+ if (!isCurrentDomain) {
509
+ return;
510
+ }
511
+ }
512
+ return stateData.user;
382
513
  }
383
514
  /**
384
515
  * Retrieve the user session from the store, or undefined if no session found.
385
516
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
386
- * @returns The sessionm or undefined if no session found in the store.
517
+ * @returns The session or undefined if no session found in the store.
387
518
  */
388
519
  async getSession(storeOptions) {
389
520
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
390
521
  if (stateData) {
522
+ if (this.#isResolverMode()) {
523
+ const isCurrentDomain = await this.#isSessionForCurrentDomain(stateData, storeOptions);
524
+ if (!isCurrentDomain) {
525
+ return;
526
+ }
527
+ }
391
528
  const { internal, ...sessionData } = stateData;
392
529
  return sessionData;
393
530
  }
@@ -395,24 +532,29 @@ var ServerClient = class {
395
532
  /**
396
533
  * 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.
397
534
  * Also updates the store when a new token was retrieved from Auth0.
398
- *
399
- * @param options Optional options for requesting specific audience/scope.
400
535
  * @param storeOptions Optional options used to pass to the Transaction and State Store.
401
536
  *
402
537
  * @throws {TokenByRefreshTokenError} If the refresh token was not found or there was an issue requesting the access token.
403
538
  *
404
539
  * @returns The Token Set, containing the access token, as well as additional information.
405
540
  */
406
- async getAccessToken(tokenOptionsOrStoreOptions, storeOptions) {
407
- const hasTokenOptions = (
408
- // If second arg exists, first arg must be GetAccessTokenOptions
409
- storeOptions !== void 0 || // OR if first arg has audience/scope properties
410
- tokenOptionsOrStoreOptions && typeof tokenOptionsOrStoreOptions === "object" && ("audience" in tokenOptionsOrStoreOptions || "scope" in tokenOptionsOrStoreOptions)
411
- );
412
- const [resolvedOptions, resolvedStoreOptions] = hasTokenOptions ? [tokenOptionsOrStoreOptions, storeOptions] : [void 0, tokenOptionsOrStoreOptions];
413
- const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
414
- const audience = resolvedOptions?.audience ?? this.#options.authorizationParams?.audience ?? "default";
415
- const scope = resolvedOptions?.scope ?? this.#options.authorizationParams?.scope;
541
+ async getAccessToken(storeOptions) {
542
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
543
+ const audience = this.#options.authorizationParams?.audience ?? "default";
544
+ const scope = this.#options.authorizationParams?.scope;
545
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
546
+ if (this.#isResolverMode()) {
547
+ if (!stateData) {
548
+ throw new MissingSessionError("Unable to retrieve access token without a logged in user.");
549
+ }
550
+ if (!sessionDomain) {
551
+ throw new MissingSessionError("Session domain does not match the current domain.");
552
+ }
553
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
554
+ if (sessionDomain !== resolvedDomain) {
555
+ throw new MissingSessionError("Session domain does not match the current domain.");
556
+ }
557
+ }
416
558
  const tokenSet = stateData?.tokenSets.find(
417
559
  (tokenSet2) => tokenSet2.audience === audience && (!scope || compareScopes(tokenSet2.scope, scope))
418
560
  );
@@ -424,14 +566,15 @@ var ServerClient = class {
424
566
  "The access token has expired and a refresh token was not provided. The user needs to re-authenticate."
425
567
  );
426
568
  }
427
- const tokenByRefreshTokenOptions = {
428
- refreshToken: stateData.refreshToken,
429
- ...hasTokenOptions && { audience, scope }
430
- };
431
- const tokenEndpointResponse = await this.authClient.getTokenByRefreshToken(tokenByRefreshTokenOptions);
432
- const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, resolvedStoreOptions);
433
- const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse);
434
- await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, resolvedStoreOptions);
569
+ const domainForSession = sessionDomain;
570
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenByRefreshToken({
571
+ refreshToken: stateData.refreshToken
572
+ });
573
+ const existingStateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
574
+ const updatedStateData = updateStateData(audience, existingStateData, tokenEndpointResponse, {
575
+ domain: domainForSession
576
+ });
577
+ await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
435
578
  return {
436
579
  accessToken: tokenEndpointResponse.accessToken,
437
580
  scope: tokenEndpointResponse.scope,
@@ -456,6 +599,19 @@ var ServerClient = class {
456
599
  */
457
600
  async getAccessTokenForConnection(options, storeOptions) {
458
601
  const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
602
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : this.#staticDomain;
603
+ if (this.#isResolverMode()) {
604
+ if (!stateData) {
605
+ throw new MissingSessionError("Unable to retrieve an access token for a connection without a logged in user.");
606
+ }
607
+ if (!sessionDomain) {
608
+ throw new MissingSessionError("Session domain does not match the current domain.");
609
+ }
610
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
611
+ if (sessionDomain !== resolvedDomain) {
612
+ throw new MissingSessionError("Session domain does not match the current domain.");
613
+ }
614
+ }
459
615
  const connectionTokenSet = stateData?.connectionTokenSets?.find(
460
616
  (tokenSet) => tokenSet.connection === options.connection
461
617
  );
@@ -467,12 +623,20 @@ var ServerClient = class {
467
623
  "A refresh token was not found but is required to be able to retrieve an access token for a connection."
468
624
  );
469
625
  }
470
- const tokenEndpointResponse = await this.authClient.getTokenForConnection({
626
+ const domainForSession = sessionDomain;
627
+ const tokenEndpointResponse = await this.#getAuthClient(domainForSession).getTokenForConnection({
471
628
  connection: options.connection,
472
629
  loginHint: options.loginHint,
473
630
  refreshToken: stateData.refreshToken
474
631
  });
475
- const updatedStateData = updateStateDataForConnectionTokenSet(options, stateData, tokenEndpointResponse);
632
+ const updatedStateData = updateStateDataForConnectionTokenSet(
633
+ options,
634
+ {
635
+ ...stateData,
636
+ domain: stateData.domain ?? domainForSession
637
+ },
638
+ tokenEndpointResponse
639
+ );
476
640
  await this.#stateStore.set(this.#stateStoreIdentifier, updatedStateData, false, storeOptions);
477
641
  return {
478
642
  accessToken: tokenEndpointResponse.accessToken,
@@ -489,8 +653,21 @@ var ServerClient = class {
489
653
  * @returns {URL}
490
654
  */
491
655
  async logout(options, storeOptions) {
492
- await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
493
- return this.authClient.buildLogoutUrl(options);
656
+ if (!this.#isResolverMode()) {
657
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
658
+ return this.authClient.buildLogoutUrl(options);
659
+ }
660
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
661
+ const authClient = this.#getAuthClient(resolvedDomain);
662
+ const stateData = await this.#stateStore.get(this.#stateStoreIdentifier, storeOptions);
663
+ const sessionDomain = stateData ? this.#getSessionDomain(stateData) : void 0;
664
+ if (!stateData) {
665
+ return authClient.buildLogoutUrl(options);
666
+ }
667
+ if (sessionDomain && sessionDomain === resolvedDomain) {
668
+ await this.#stateStore.delete(this.#stateStoreIdentifier, storeOptions);
669
+ }
670
+ return authClient.buildLogoutUrl(options);
494
671
  }
495
672
  /**
496
673
  * Handles the backchannel logout process by verifying the logout token and deleting the session from the store if the logout token was considered valid.
@@ -504,8 +681,23 @@ var ServerClient = class {
504
681
  if (!logoutToken) {
505
682
  throw new BackchannelLogoutError("Missing Logout Token");
506
683
  }
507
- const logoutTokenClaims = await this.authClient.verifyLogoutToken({ logoutToken });
508
- await this.#stateStore.deleteByLogoutToken(logoutTokenClaims, storeOptions);
684
+ if (!this.#isResolverMode()) {
685
+ const logoutTokenClaims2 = await this.authClient.verifyLogoutToken({ logoutToken });
686
+ await this.#stateStore.deleteByLogoutToken(logoutTokenClaims2, storeOptions);
687
+ return;
688
+ }
689
+ const issuer = decodeIssuer(logoutToken);
690
+ if (!issuer) {
691
+ throw new BackchannelLogoutError("Logout token is missing an issuer");
692
+ }
693
+ const resolvedDomain = await this.#resolveDomain(storeOptions);
694
+ const domain = normalizeDomain(issuer);
695
+ if (domain !== resolvedDomain) {
696
+ throw new BackchannelLogoutError("Logout token issuer does not match the resolved domain");
697
+ }
698
+ const authClient = this.#getAuthClient(domain);
699
+ const logoutTokenClaims = await authClient.verifyLogoutToken({ logoutToken });
700
+ await this.#stateStore.deleteByLogoutToken({ ...logoutTokenClaims, iss: issuer }, storeOptions);
509
701
  }
510
702
  };
511
703
 
@@ -829,6 +1021,7 @@ export {
829
1021
  BackchannelLogoutError,
830
1022
  CookieTransactionStore,
831
1023
  InvalidConfigurationError,
1024
+ IssuerValidationError,
832
1025
  MissingRequiredArgumentError,
833
1026
  MissingSessionError,
834
1027
  MissingTransactionError,