@mastra/auth-better-auth 1.1.2-alpha.1 → 1.1.2-alpha.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @mastra/auth-better-auth
2
2
 
3
+ ## 1.1.2-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Added a deferred instance mode and organization management to MastraAuthBetterAuth so it can be passed directly to a server host without a wrapper adapter. The provider can now be constructed with just a secret and will build its Better Auth instance (including running migrations) against the host database during init. It also bootstraps a personal organization for new users (ensureOrganization), checks organization admin roles (isOrganizationAdmin), and exposes the Better Auth HTTP handler (handleAuthRequest) so hosts can mount it under /auth/api/*. ([#19765](https://github.com/mastra-ai/mastra/pull/19765))
8
+
9
+ **Before**
10
+
11
+ ```ts
12
+ import { betterAuth } from 'better-auth';
13
+ import { MastraAuthBetterAuth } from '@mastra/auth-better-auth';
14
+
15
+ const auth = new MastraAuthBetterAuth({ auth: betterAuth({/* ... */}) });
16
+ ```
17
+
18
+ **After** (bring-your-own instance still works)
19
+
20
+ ```ts
21
+ import { MastraAuthBetterAuth } from '@mastra/auth-better-auth';
22
+
23
+ const auth = new MastraAuthBetterAuth({ secret: process.env.BETTER_AUTH_SECRET! });
24
+ // host calls auth.init({ database, publicUrl, allowedOrigins }) during startup
25
+ ```
26
+
3
27
  ## 1.1.2-alpha.1
4
28
 
5
29
  ### Patch Changes
@@ -3,12 +3,9 @@
3
3
  *
4
4
  * @license Mastra Enterprise License - see ee/LICENSE
5
5
  */
6
- import type { FGACheckContext, IFGAProvider } from './interfaces/fga.js';
6
+ import type { ActorSignal, FGACheckContext, IFGAProvider } from './interfaces/fga.js';
7
7
  import type { MastraFGAPermissionInput } from './interfaces/permissions.generated.js';
8
- export type ActorSignal = true | {
9
- actorKind: 'system';
10
- sourceWorkflow?: string;
11
- };
8
+ export type { ActorSignal };
12
9
  export interface CheckFGAOptions {
13
10
  fgaProvider: IFGAProvider | undefined;
14
11
  user: any;
@@ -8,6 +8,34 @@
8
8
  * @license Mastra Enterprise License - see ee/LICENSE
9
9
  */
10
10
  import type { MastraFGAPermissionInput } from './permissions.generated.js';
11
+ /**
12
+ * Signals that a call is made by a trusted non-user actor rather than an
13
+ * authenticated end user.
14
+ *
15
+ * - `true` is the anonymous system shorthand.
16
+ * - The object form additionally names the acting agent (`agentId`) and carries
17
+ * the permission grants / scope a provider can enforce for least privilege.
18
+ */
19
+ export type ActorSignal = true | {
20
+ actorKind: 'system';
21
+ sourceWorkflow?: string;
22
+ /**
23
+ * Identity of the acting system agent. Unlike the check `resource` (the
24
+ * target), this names the actor itself so a provider can enforce
25
+ * per-agent least privilege.
26
+ */
27
+ agentId?: string;
28
+ /**
29
+ * Permission grants *claimed* for this actor — the actor analog of a
30
+ * user's resolved permissions. This is an untrusted, self-asserted hint:
31
+ * a provider enforcing real least privilege should resolve the actor's
32
+ * authoritative grants from a trusted source (e.g. a manifest or FGA,
33
+ * keyed by `agentId`) rather than trusting these values directly.
34
+ */
35
+ permissions?: MastraFGAPermissionInput[];
36
+ /** Additional provider-specific scope for the actor (e.g. tenant, environment). */
37
+ scope?: Record<string, string>;
38
+ };
11
39
  /**
12
40
  * Optional context for an authorization check.
13
41
  */
@@ -306,6 +334,25 @@ export interface IFGAProvider<TUser = unknown> {
306
334
  filterAccessible<T extends {
307
335
  id: string;
308
336
  }>(user: TUser, resources: T[], resourceType: string, permission: MastraFGAPermissionInput): Promise<T[]>;
337
+ /**
338
+ * Authorize a non-user (system / autonomous agent) actor.
339
+ *
340
+ * System actors bypass the user-centric `require()` path. Implement this
341
+ * method to opt in to provider-driven least privilege for those actors:
342
+ * decide whether the actor (identified by `actor.agentId` and constrained by
343
+ * `actor.permissions` / `actor.scope`) may perform `permission` on
344
+ * `resource`, and throw {@link FGADeniedError} to deny.
345
+ *
346
+ * When this method is not implemented, Mastra preserves the legacy
347
+ * trusted-actor bypass (allow after the tenant-scope check). Adding it is
348
+ * therefore fully backward compatible.
349
+ *
350
+ * @param actor - The system actor signal (`true` shorthand, or an object with
351
+ * `agentId`, `permissions`, `scope`).
352
+ * @param params - The resource and permission being attempted.
353
+ * @throws FGADeniedError if the actor may not perform the action.
354
+ */
355
+ requireActor?(actor: ActorSignal, params: FGACheckParams): Promise<void>;
309
356
  }
310
357
  /**
311
358
  * Extended FGA interface with write operations for managing resources and role assignments.
@@ -270,6 +270,72 @@ export interface ICredentialsProvider<TUser extends User = User> {
270
270
  */
271
271
  isSignUpEnabled?(): boolean;
272
272
  }
273
+ /**
274
+ * Provider interface for organization membership management.
275
+ *
276
+ * Implement this interface to enable multi-tenant hosts (e.g. the Mastra Code
277
+ * web factory) to bootstrap a personal organization for new users and to
278
+ * authorize organization-level administrative mutations.
279
+ */
280
+ export interface IOrganizationsProvider {
281
+ /**
282
+ * Ensure the user belongs to an organization, bootstrapping a personal org
283
+ * on first use when they have none. Must be idempotent under
284
+ * concurrent/retried first logins.
285
+ *
286
+ * @param userId - Stable provider user id
287
+ * @returns The organization id, or `undefined` when the user genuinely
288
+ * stays no-org (bootstrap is best-effort)
289
+ */
290
+ ensureOrganization(userId: string): Promise<string | undefined>;
291
+ /**
292
+ * Whether the user holds an admin-equivalent role in the organization.
293
+ * Provider errors should resolve to `false` rather than throw.
294
+ *
295
+ * @param organizationId - Organization id
296
+ * @param userId - Stable provider user id
297
+ */
298
+ isOrganizationAdmin(organizationId: string, userId: string): Promise<boolean>;
299
+ }
300
+ /**
301
+ * Host-level context handed to {@link IAuthInit.init} once during server
302
+ * preparation. Storage-agnostic: hosts pass whatever database handle their
303
+ * storage backend exposes.
304
+ */
305
+ export interface AuthInitContext {
306
+ /**
307
+ * Database handle for providers that persist their own auth tables
308
+ * (e.g. better-auth). Shape is provider-defined; hosts pass their storage
309
+ * backend's auth database as-is.
310
+ */
311
+ database?: unknown;
312
+ /** Browser-facing origin (no trailing slash), e.g. `https://factory.acme.com`. */
313
+ publicUrl?: string;
314
+ /**
315
+ * Extra browser origins allowed to talk to the API (cross-origin SPA
316
+ * deploys). Providers that enforce their own origin allow-list (e.g.
317
+ * better-auth `trustedOrigins`) must honor these.
318
+ */
319
+ allowedOrigins?: string[];
320
+ }
321
+ /**
322
+ * Optional one-time initialization hook for auth providers. Hosts call
323
+ * `init()` once during preparation with host-level context (database, public
324
+ * origin) so providers can consume it without the deploy entry passing it
325
+ * twice. Providers should fail fast here for requirements only satisfiable at
326
+ * prepare time.
327
+ */
328
+ export interface IAuthInit {
329
+ init(ctx: AuthInitContext): Promise<void>;
330
+ }
331
+ /**
332
+ * Provider interface for handling raw auth HTTP requests. Providers that ship
333
+ * their own HTTP API surface (e.g. better-auth's `/api/auth/*` handler)
334
+ * implement this so hosts can mount it (typically under `/auth/api/*`).
335
+ */
336
+ export interface IAuthHttpHandler {
337
+ handleAuthRequest(request: Request): Promise<Response>;
338
+ }
273
339
  export * from './session/index.js';
274
340
  export * from './provider/index.js';
275
341
  export * from './types/index.js';
@@ -1,5 +1,5 @@
1
1
  import { MastraBase } from '../_types/@internal_core/dist/base/index.d.ts';
2
- import type { CredentialsResult, ISSOProvider, ISessionProvider, IUserProvider, Session, SSOCallbackResult, SSOLoginConfig, User } from '..';
2
+ import type { CredentialsResult, IAuthHttpHandler, IAuthInit, ICredentialsProvider, IOrganizationsProvider, ISSOProvider, ISessionProvider, IUserProvider, Session, SSOCallbackResult, SSOLoginConfig, User } from '..';
3
3
  import type { AuthorizeUserFn, MastraAuthConfig, MastraAuthRequest } from '../types/index.js';
4
4
  export interface MastraAuthProviderOptions<TUser = unknown> {
5
5
  name?: string;
@@ -72,6 +72,13 @@ export declare abstract class MastraAuthProvider<TUser = unknown> extends Mastra
72
72
  abstract authorizeUser(user: TUser, request: MastraAuthRequest): Promise<boolean> | boolean;
73
73
  protected registerOptions(opts?: MastraAuthProviderOptions<TUser>): void;
74
74
  }
75
+ export declare function isSSOProvider(p: unknown): p is ISSOProvider;
76
+ export declare function isSessionProvider(p: unknown): p is ISessionProvider;
77
+ export declare function isUserProvider(p: unknown): p is IUserProvider;
78
+ export declare function isCredentialsProvider(p: unknown): p is ICredentialsProvider;
79
+ export declare function isOrganizationsProvider(p: unknown): p is IOrganizationsProvider;
80
+ export declare function isAuthHttpHandler(p: unknown): p is IAuthHttpHandler;
81
+ export declare function hasAuthInit(p: unknown): p is IAuthInit;
75
82
  export declare class CompositeAuth extends MastraAuthProvider implements ISSOProvider<User>, ISessionProvider<Session>, IUserProvider<User> {
76
83
  private providers;
77
84
  private authenticatedProviderByObject;
package/dist/index.cjs CHANGED
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('better-auth/crypto');
3
+ var kyselyLibsql = require('@libsql/kysely-libsql');
4
+ var betterAuth = require('better-auth');
5
+ var crypto$1 = require('better-auth/crypto');
6
+ var migration = require('better-auth/db/migration');
7
+ var plugins = require('better-auth/plugins');
4
8
 
5
9
  // ../../packages/_internal-core/dist/chunk-3M4SEWMI.js
6
10
  var RegisteredLogger = {
@@ -154,7 +158,7 @@ var MastraBase = class {
154
158
  }
155
159
  };
156
160
 
157
- // ../../packages/_internals/auth/dist/chunk-7GLKLOU3.js
161
+ // ../../packages/_internals/auth/dist/chunk-NJOKT6V5.js
158
162
  var MastraAuthProvider = class extends MastraBase {
159
163
  protected;
160
164
  public;
@@ -209,23 +213,253 @@ function mapBetterAuthUserToEEUser(user) {
209
213
  };
210
214
  }
211
215
  var MastraAuthBetterAuth = class extends MastraAuthProvider {
212
- auth;
216
+ #auth;
217
+ #secret;
218
+ /** True when `init()` built the instance — then we also own its migrations. */
219
+ #ownsInstance = false;
220
+ /** Once-per-process migration latch; reset on failure so a later call retries. */
221
+ #migrated;
222
+ /** In-process `userId → orgId` cache so hosts can call `ensureOrganization` per request. */
223
+ #orgCache = /* @__PURE__ */ new Map();
224
+ /** Set from `init()`: cross-origin SPA deploys need SameSite=None; Secure cookies. */
225
+ #crossSite = false;
213
226
  signUpEnabledConfig;
214
- sessionCookieName;
215
227
  constructor(options) {
216
228
  super({ name: options?.name ?? "better-auth" });
217
- if (!options.auth) {
229
+ if (!options.auth && !options.secret) {
218
230
  throw new Error(
219
- "Better Auth instance is required. Please provide the auth option with your Better Auth instance created via betterAuth({ ... })"
231
+ "Better Auth instance is required. Please provide the auth option with your Better Auth instance created via betterAuth({ ... }), or provide `secret` so the provider can build its own instance in init() on the host database."
220
232
  );
221
233
  }
222
- this.auth = options.auth;
234
+ this.#auth = options.auth;
235
+ this.#secret = options.secret;
223
236
  this.signUpEnabledConfig = options.signUpEnabled ?? true;
224
- const authWithOptions = this.auth;
225
- const prefix = authWithOptions.options?.advanced?.cookiePrefix ?? "better-auth";
226
- this.sessionCookieName = `${prefix}.session_token`;
227
237
  this.registerOptions(options);
228
238
  }
239
+ /**
240
+ * The active Better Auth instance. Throws before `init()` in deferred
241
+ * instance mode (constructed with `secret` instead of `auth`).
242
+ */
243
+ get auth() {
244
+ if (!this.#auth) {
245
+ throw new Error(
246
+ "MastraAuthBetterAuth is not initialized \u2014 init() must run first (or pass a configured `auth` instance)."
247
+ );
248
+ }
249
+ return this.#auth;
250
+ }
251
+ /**
252
+ * Session cookie name, honoring Better Auth's `cookiePrefix`, a caller
253
+ * override via `advanced.cookies.session_token.name`, and the `__Secure-`
254
+ * prefix Better Auth applies when secure cookies are active.
255
+ */
256
+ get sessionCookieName() {
257
+ const options = this.#auth?.options;
258
+ const advanced = options?.advanced;
259
+ const prefix = advanced?.cookiePrefix ?? "better-auth";
260
+ const secure = advanced?.useSecureCookies ?? options?.baseURL?.startsWith("https://") ?? false;
261
+ const baseName = advanced?.cookies?.session_token?.name ?? `${prefix}.session_token`;
262
+ return `${secure ? "__Secure-" : ""}${baseName}`;
263
+ }
264
+ // ============================================
265
+ // IAuthInit implementation
266
+ // ============================================
267
+ /**
268
+ * One-time host initialization. In deferred instance mode (no `auth` in the
269
+ * options) this builds the provider-owned `betterAuth()` instance on the
270
+ * host's auth database; schema migrations then run lazily behind a
271
+ * once-per-process latch on first use.
272
+ *
273
+ * Bring-your-own `auth` instances skip construction entirely — the caller
274
+ * owns their database and migrations.
275
+ */
276
+ async init(ctx) {
277
+ this.#crossSite = (ctx.allowedOrigins?.length ?? 0) > 0;
278
+ if (this.#auth) return;
279
+ const authDb = ctx.database;
280
+ if (!authDb) {
281
+ throw new Error(
282
+ "MastraAuthBetterAuth needs a database to build its own better-auth instance, but the host passed none. Use a storage backend that exposes an auth database, or pass your own configured `auth` instance."
283
+ );
284
+ }
285
+ const database = authDb.dialect === "postgres" ? authDb.pool : authDb.dialect === "libsql" ? {
286
+ dialect: new kyselyLibsql.LibsqlDialect({ client: authDb.client }),
287
+ type: "sqlite"
288
+ } : authDb.database;
289
+ const allowedOrigins = ctx.allowedOrigins ?? [];
290
+ const options = {
291
+ database,
292
+ secret: this.#secret,
293
+ // All provider endpoints (sign-in/up/out/session) live under /auth/api/*,
294
+ // where hosts mount handleAuthRequest.
295
+ basePath: "/auth/api",
296
+ ...ctx.publicUrl ? { baseURL: ctx.publicUrl } : {},
297
+ // Cross-origin SPA deploys: SameSite=None only lets the browser SEND the
298
+ // cookie — better-auth still rejects requests from origins outside its
299
+ // own allow-list, so the SPA origins must be trusted too.
300
+ ...allowedOrigins.length ? { trustedOrigins: allowedOrigins } : {},
301
+ emailAndPassword: { enabled: true, disableSignUp: !this.signUpEnabledConfig },
302
+ plugins: [plugins.organization()],
303
+ // Cross-origin SPA deploys need SameSite=None; Secure for the browser to
304
+ // send the session cookie.
305
+ ...this.#crossSite ? { advanced: { defaultCookieAttributes: { sameSite: "none", secure: true } } } : {}
306
+ };
307
+ this.#auth = betterAuth.betterAuth(options);
308
+ this.#ownsInstance = true;
309
+ }
310
+ /**
311
+ * Ensure better-auth's tables exist in the host database. Only for instances
312
+ * this provider built — bring-your-own instances manage their own migrations.
313
+ */
314
+ async #ensureDbReady() {
315
+ if (!this.#ownsInstance) return;
316
+ this.#migrated ??= (async () => {
317
+ const { runMigrations } = await migration.getMigrations(this.auth.options);
318
+ await runMigrations();
319
+ })();
320
+ try {
321
+ await this.#migrated;
322
+ } catch (error) {
323
+ this.#migrated = void 0;
324
+ console.warn("[BetterAuth] Failed to run auth schema migrations; auth stays unavailable until this succeeds.");
325
+ throw error;
326
+ }
327
+ }
328
+ // ============================================
329
+ // IAuthHttpHandler implementation
330
+ // ============================================
331
+ /**
332
+ * Proxy a raw HTTP request to Better Auth's own API surface
333
+ * (sign-in/up/out/session). Hosts mount this under `/auth/api/*`.
334
+ */
335
+ async handleAuthRequest(request) {
336
+ try {
337
+ await this.#ensureDbReady();
338
+ } catch {
339
+ return new Response(JSON.stringify({ error: "auth_unavailable" }), {
340
+ status: 503,
341
+ headers: { "content-type": "application/json" }
342
+ });
343
+ }
344
+ return this.auth.handler(request);
345
+ }
346
+ // ============================================
347
+ // IOrganizationsProvider implementation
348
+ // ============================================
349
+ /**
350
+ * Ensure the user belongs to an organization, mirroring the WorkOS
351
+ * personal-org bootstrap on better-auth's organization tables:
352
+ * ≥1 membership → first org id; 0 → create a personal org with an
353
+ * idempotent slug derived from the user id.
354
+ *
355
+ * Concurrent/retried first logins recover via the unique slug instead of
356
+ * creating duplicates. Slug alone is NOT proof of ownership though: the
357
+ * organization API is reachable by any authenticated user, so an attacker
358
+ * could squat `personal-<victimId>`. The slug-matched org is only adopted
359
+ * when nobody else is a member; otherwise a fresh org with an unguessable
360
+ * slug is created instead.
361
+ *
362
+ * Best-effort: any failure is swallowed and leaves the user no-org.
363
+ */
364
+ async ensureOrganization(userId) {
365
+ const cached = this.#orgCache.get(userId);
366
+ if (cached) return cached;
367
+ try {
368
+ await this.#ensureDbReady();
369
+ const ctx = await this.getAuthContext();
370
+ if (!ctx) return void 0;
371
+ const memberships = await ctx.adapter.findMany({
372
+ model: "member",
373
+ where: [{ field: "userId", value: userId }]
374
+ });
375
+ const firstExisting = memberships.find((m) => m.organizationId)?.organizationId;
376
+ if (firstExisting) {
377
+ this.#orgCache.set(userId, firstExisting);
378
+ return firstExisting;
379
+ }
380
+ const userRecord = await ctx.internalAdapter.findUserById(userId).catch(() => null);
381
+ const label = userRecord?.email ?? userRecord?.name ?? userId;
382
+ const orgName = `${label}'s org`;
383
+ const orgData = () => ({
384
+ name: orgName,
385
+ createdAt: /* @__PURE__ */ new Date(),
386
+ metadata: JSON.stringify({ mastraPersonalOrg: "true" })
387
+ });
388
+ const slug = `personal-${userId}`;
389
+ let organizationId;
390
+ try {
391
+ const created = await ctx.adapter.create({
392
+ model: "organization",
393
+ data: { ...orgData(), slug }
394
+ });
395
+ organizationId = created.id;
396
+ } catch (error) {
397
+ const existing = await ctx.adapter.findOne({
398
+ model: "organization",
399
+ where: [{ field: "slug", value: slug }]
400
+ });
401
+ if (!existing) throw error;
402
+ const existingMembers = await ctx.adapter.findMany({
403
+ model: "member",
404
+ where: [{ field: "organizationId", value: existing.id }]
405
+ });
406
+ const foreignMember = existingMembers.some((m) => m.userId !== userId);
407
+ if (foreignMember) {
408
+ const fallback = await ctx.adapter.create({
409
+ model: "organization",
410
+ data: { ...orgData(), slug: `personal-${userId}-${crypto.randomUUID()}` }
411
+ });
412
+ organizationId = fallback.id;
413
+ } else {
414
+ organizationId = existing.id;
415
+ }
416
+ }
417
+ try {
418
+ await ctx.adapter.create({
419
+ model: "member",
420
+ data: { organizationId, userId, role: "owner", createdAt: /* @__PURE__ */ new Date() }
421
+ });
422
+ } catch (error) {
423
+ const member = await ctx.adapter.findOne({
424
+ model: "member",
425
+ where: [
426
+ { field: "organizationId", value: organizationId },
427
+ { field: "userId", value: userId }
428
+ ]
429
+ });
430
+ if (!member) throw error;
431
+ }
432
+ this.#orgCache.set(userId, organizationId);
433
+ return organizationId;
434
+ } catch (error) {
435
+ console.warn(
436
+ `[BetterAuth] Failed to bootstrap personal organization for user ${userId}. The user will see organization_required until this succeeds.`,
437
+ error
438
+ );
439
+ return void 0;
440
+ }
441
+ }
442
+ /**
443
+ * Whether the user holds an admin-equivalent role (`owner` or `admin`) in
444
+ * the organization. Provider errors resolve to `false`.
445
+ */
446
+ async isOrganizationAdmin(organizationId, userId) {
447
+ try {
448
+ await this.#ensureDbReady();
449
+ const ctx = await this.getAuthContext();
450
+ if (!ctx) return false;
451
+ const membership = await ctx.adapter.findOne({
452
+ model: "member",
453
+ where: [
454
+ { field: "organizationId", value: organizationId },
455
+ { field: "userId", value: userId }
456
+ ]
457
+ });
458
+ return membership?.role === "owner" || membership?.role === "admin";
459
+ } catch {
460
+ return false;
461
+ }
462
+ }
229
463
  /**
230
464
  * Check if sign-up is enabled.
231
465
  * Implements ICredentialsProvider.isSignUpEnabled.
@@ -249,6 +483,7 @@ var MastraAuthBetterAuth = class extends MastraAuthProvider {
249
483
  */
250
484
  async getCurrentUser(request) {
251
485
  try {
486
+ await this.#ensureDbReady();
252
487
  const headers = new Headers(request.headers);
253
488
  const authHeader = headers.get("Authorization");
254
489
  const bearerToken = authHeader && authHeader.slice(0, 7).toLowerCase() === "bearer " ? authHeader.slice(7).trim() : void 0;
@@ -333,7 +568,7 @@ var MastraAuthBetterAuth = class extends MastraAuthProvider {
333
568
  if (hasSessionCookie) return;
334
569
  let cookieValue = token.includes(".") ? token.includes("%") ? tryDecode(token) : token : token;
335
570
  if (!token.includes(".") && secret) {
336
- cookieValue = `${token}.${await crypto.makeSignature(token, secret)}`;
571
+ cookieValue = `${token}.${await crypto$1.makeSignature(token, secret)}`;
337
572
  }
338
573
  const existingCookies = cookieHeader ? `${cookieHeader}; ` : "";
339
574
  headers.set("Cookie", `${existingCookies}${cookieName}=${encodeURIComponent(cookieValue)}`);
@@ -352,6 +587,7 @@ var MastraAuthBetterAuth = class extends MastraAuthProvider {
352
587
  */
353
588
  async authenticateToken(token, request) {
354
589
  try {
590
+ await this.#ensureDbReady();
355
591
  const headers = new Headers();
356
592
  const cookieHeader = getRequestHeader2(request, "Cookie");
357
593
  if (cookieHeader) {
@@ -477,9 +713,10 @@ var MastraAuthBetterAuth = class extends MastraAuthProvider {
477
713
  * Clears Better Auth's default session cookies.
478
714
  */
479
715
  getClearSessionHeaders() {
716
+ const sameSite = this.#crossSite ? "None; Secure" : "Lax";
480
717
  const cookies = [
481
- `${this.sessionCookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`,
482
- `${this.sessionCookieName}_sig=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`
718
+ `${this.sessionCookieName}=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`,
719
+ `${this.sessionCookieName}_sig=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`
483
720
  ];
484
721
  return {
485
722
  "Set-Cookie": cookies.join(", ")