[GH-ISSUE #7878] customSession plugin comma-joins Set-Cookie headers when cookieCache is enabled, causing premature session expiry #10937

Closed
opened 2026-04-13 07:19:55 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @thomaspeklak on GitHub (Feb 9, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7878

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure better-auth with the customSession plugin and cookieCache enabled:
    import { betterAuth } from "better-auth";
    import { customSession } from "better-auth/plugins";
    
    export const auth = betterAuth({
      session: {
        expiresIn: 60 * 60 * 24 * 7, // 7 days
        cookieCache: {
          enabled: true,
          maxAge: 60 * 5, // 5 minutes
        },
      },
      plugins: [
        customSession(async ({ user, session }) => {
          return { user, session, custom: "data" };
        }),
      ],
    });
    
  2. Sign in a user and call getSession from the client
  3. Inspect the Set-Cookie response headers in the browser DevTools (Network tab)
  4. Observe that session_token and session_data are comma-joined into a single Set-Cookie header instead of being separate headers
  5. The session_token cookie silently inherits the short Max-Age (5 minutes) from the session_data cookie, and the user is logged out after 5 minutes

Current vs. Expected behavior

Current: customSession uses Headers.get("set-cookie") to forward cookies from the internal getSession() call. Per the Web API spec, Headers.get("set-cookie") comma-joins multiple Set-Cookie values into a single string:

better-auth.session_token=xxx; Max-Age=604800; Path=/; HttpOnly; SameSite=Lax, better-auth.session_data=yyy; Max-Age=300; Path=/; HttpOnly; SameSite=Lax

The browser receives this as one Set-Cookie header and parses only the first cookie name/value. The Max-Age=300 from session_data overrides Max-Age=604800 from session_token, causing the session token to expire after 5 minutes instead of 7 days.

Expected: Each Set-Cookie should be its own separate response header. The fix is to use Headers.getSetCookie() (which returns an array of individual cookie strings) and forward each one with Headers.append():

const setCookieHeaders = session.headers.getSetCookie();
for (const cookie of setCookieHeaders) {
  responseHeaders.append("set-cookie", cookie);
}

This is documented on MDN: Headers.getSetCookie().

What version of Better Auth are you using?

1.5.0-beta.13 (also affects 1.4.18 stable and all versions since PR #2124)

System info

{
  "os": "Linux 6.18.5-200.fc43.x86_64",
  "node": "v22.22.0",
  "betterAuth": "1.5.0-beta.13"
}

Which area(s) are affected? (Select all that apply)

Package

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,
    cookieCache: {
      enabled: true,
      maxAge: 60 * 5,
    },
  },
  plugins: [
    customSession(async ({ user, session }) => {
      return { user, session };
    }),
  ],
});

Additional context

This bug is latent in any setup with customSession alone (single Set-Cookie header works fine). It only manifests when cookieCache is also enabled, because that's when two Set-Cookie headers are set (session_token + session_data).

The symptom is silent — no errors, no warnings. The session exists in the database, but the browser stops sending the cookie because it expired client-side. Users experience seemingly random logouts after the cookieCache.maxAge duration.

The bug was introduced in PR #2124 (April 2025) and persisted through PR #4388 (September 2025) which attempted to fix cookie handling but still used Headers.get("set-cookie").

Node.js REPL proof:

const h = new Headers();
h.append("set-cookie", "session_token=abc; Max-Age=604800; HttpOnly");
h.append("set-cookie", "session_data=xyz; Max-Age=300; HttpOnly");

h.get("set-cookie");
// "session_token=abc; Max-Age=604800; HttpOnly, session_data=xyz; Max-Age=300; HttpOnly"

h.getSetCookie();
// ["session_token=abc; Max-Age=604800; HttpOnly", "session_data=xyz; Max-Age=300; HttpOnly"]
Originally created by @thomaspeklak on GitHub (Feb 9, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7878 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure better-auth with the `customSession` plugin and `cookieCache` enabled: ```typescript import { betterAuth } from "better-auth"; import { customSession } from "better-auth/plugins"; export const auth = betterAuth({ session: { expiresIn: 60 * 60 * 24 * 7, // 7 days cookieCache: { enabled: true, maxAge: 60 * 5, // 5 minutes }, }, plugins: [ customSession(async ({ user, session }) => { return { user, session, custom: "data" }; }), ], }); ``` 2. Sign in a user and call `getSession` from the client 3. Inspect the `Set-Cookie` response headers in the browser DevTools (Network tab) 4. Observe that `session_token` and `session_data` are comma-joined into a single `Set-Cookie` header instead of being separate headers 5. The `session_token` cookie silently inherits the short `Max-Age` (5 minutes) from the `session_data` cookie, and the user is logged out after 5 minutes ### Current vs. Expected behavior **Current:** `customSession` uses `Headers.get("set-cookie")` to forward cookies from the internal `getSession()` call. Per the Web API spec, `Headers.get("set-cookie")` comma-joins multiple Set-Cookie values into a single string: ``` better-auth.session_token=xxx; Max-Age=604800; Path=/; HttpOnly; SameSite=Lax, better-auth.session_data=yyy; Max-Age=300; Path=/; HttpOnly; SameSite=Lax ``` The browser receives this as one `Set-Cookie` header and parses only the first cookie name/value. The `Max-Age=300` from `session_data` overrides `Max-Age=604800` from `session_token`, causing the session token to expire after 5 minutes instead of 7 days. **Expected:** Each `Set-Cookie` should be its own separate response header. The fix is to use `Headers.getSetCookie()` (which returns an array of individual cookie strings) and forward each one with `Headers.append()`: ```typescript const setCookieHeaders = session.headers.getSetCookie(); for (const cookie of setCookieHeaders) { responseHeaders.append("set-cookie", cookie); } ``` This is documented on [MDN: Headers.getSetCookie()](https://developer.mozilla.org/en-US/docs/Web/API/Headers/getSetCookie). ### What version of Better Auth are you using? 1.5.0-beta.13 (also affects 1.4.18 stable and all versions since PR #2124) ### System info ```bash { "os": "Linux 6.18.5-200.fc43.x86_64", "node": "v22.22.0", "betterAuth": "1.5.0-beta.13" } ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { customSession } from "better-auth/plugins"; export const auth = betterAuth({ emailAndPassword: { enabled: true, }, session: { expiresIn: 60 * 60 * 24 * 7, cookieCache: { enabled: true, maxAge: 60 * 5, }, }, plugins: [ customSession(async ({ user, session }) => { return { user, session }; }), ], }); ``` ### Additional context This bug is latent in any setup with `customSession` alone (single Set-Cookie header works fine). It only manifests when `cookieCache` is also enabled, because that's when two Set-Cookie headers are set (`session_token` + `session_data`). The symptom is silent — no errors, no warnings. The session exists in the database, but the browser stops sending the cookie because it expired client-side. Users experience seemingly random logouts after the `cookieCache.maxAge` duration. The bug was introduced in PR #2124 (April 2025) and persisted through PR #4388 (September 2025) which attempted to fix cookie handling but still used `Headers.get("set-cookie")`. Node.js REPL proof: ```javascript const h = new Headers(); h.append("set-cookie", "session_token=abc; Max-Age=604800; HttpOnly"); h.append("set-cookie", "session_data=xyz; Max-Age=300; HttpOnly"); h.get("set-cookie"); // "session_token=abc; Max-Age=604800; HttpOnly, session_data=xyz; Max-Age=300; HttpOnly" h.getSetCookie(); // ["session_token=abc; Max-Age=604800; HttpOnly", "session_data=xyz; Max-Age=300; HttpOnly"] ```
GiteaMirror added the lockedbug labels 2026-04-13 07:19:55 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Feb 9, 2026):

It seems you've encountered an issue with the customSession plugin when cookieCache is enabled, leading to the Set-Cookie headers being comma-joined, thus causing premature session expiry for the session_token cookie. This behavior has been noted in the context of Better Auth and is an identified issue.

To resolve this, a potential solution is to ensure that the cookies are sent as separate headers rather than being combined. The recommended approach is to modify your code to use Headers.getSetCookie() to retrieve individual cookie strings and then append each one as needed. Here's a snippet based on your findings:

const setCookieHeaders = session.headers.getSetCookie();
for (const cookie of setCookieHeaders) {
    responseHeaders.append("set-cookie", cookie);
}

This change helps prevent any inadvertent overriding of cookie attributes, ensuring that both session_token and session_data are sent correctly and separately, thus preserving their intended expiration settings [6].

If you're using version 1.5.0-beta.13 or earlier, this behavior has been consistent through those releases. There was a fix implemented that addresses similar concerns, but if you're still facing issues, it may be worthwhile to check for updates or report further details on the GitHub issues page [1].

Is there a specific aspect you'd like assistance with, perhaps additional configurations or alternative solutions to explore?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3872904790 --> @better-auth-agent[bot] commented on GitHub (Feb 9, 2026): It seems you've encountered an issue with the `customSession` plugin when `cookieCache` is enabled, leading to the `Set-Cookie` headers being comma-joined, thus causing premature session expiry for the `session_token` cookie. This behavior has been noted in the context of Better Auth and is an identified issue. To resolve this, a potential solution is to ensure that the cookies are sent as separate headers rather than being combined. The recommended approach is to modify your code to use `Headers.getSetCookie()` to retrieve individual cookie strings and then append each one as needed. Here's a snippet based on your findings: ```typescript const setCookieHeaders = session.headers.getSetCookie(); for (const cookie of setCookieHeaders) { responseHeaders.append("set-cookie", cookie); } ``` This change helps prevent any inadvertent overriding of cookie attributes, ensuring that both `session_token` and `session_data` are sent correctly and separately, thus preserving their intended expiration settings [[6]](https://github.com/better-auth/better-auth/pull/4388). If you're using version 1.5.0-beta.13 or earlier, this behavior has been consistent through those releases. There was a fix implemented that addresses similar concerns, but if you're still facing issues, it may be worthwhile to check for updates or report further details on the GitHub issues page [[1]](https://github.com/better-auth/better-auth/issues/3713). Is there a specific aspect you'd like assistance with, perhaps additional configurations or alternative solutions to explore? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10937