[GH-ISSUE #9231] customSession plugin drops Partitioned cookie attribute on session refresh #28641

Open
opened 2026-04-17 20:03:48 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @dumptyd on GitHub (Apr 17, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/9231

Reproduction

  1. Set defaultCookieAttributes: { partitioned: true, sameSite: "none", secure: true } and use customSession plugin
  2. Sign in (cookie is correctly Partitioned)
  3. Wait for updateAge to elapse, call /get-session -- session refresh re-sets cookie without Partitioned
  4. Browser now has two same-name cookies (one partitioned, one not)
  5. Sign out -- expireCookie correctly clears the partitioned cookie but the unpartitioned one survives (or vice versa)
  6. Sign in again -- new partitioned cookie is set, but the ghost unpartitioned cookie from step 3 still exists
  7. /get-session receives both cookies, picks the wrong one, returns null

Root cause in custom-session/index.ts L112-124 -- the ctx.setCookie reconstruction explicitly maps known attributes but omits partitioned:

ctx.setCookie(name, attrs.value, {
    maxAge: attrs["max-age"],
    expires: attrs.expires,
    domain: attrs.domain,
    path: attrs.path,
    secure: attrs.secure,
    httpOnly: attrs.httponly,
    sameSite: attrs.samesite,
    // partitioned not forwarded
});

parseSetCookieHeader correctly captures partitioned via the default switch case, but the reconstruction ignores it.

Current vs. Expected behavior

Session breaks after refresh + re-login cycle when using partitioned cookies. All cookie attributes -- including Partitioned -- should be preserved when forwarding Set-Cookie headers.

What version of Better Auth are you using?

1.4.22 (also confirmed unfixed on main and v1.6.5)

System info

N/A (code-level omission, not environment-specific)

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

Backend

Auth config (if applicable)

export const auth = betterAuth({
    advanced: {
        defaultCookieAttributes: {
            partitioned: true,
            sameSite: "none",
            secure: true,
            httpOnly: true,
        },
        crossSubDomainCookies: { enabled: true },
    },
    plugins: [
        customSession(async (session) => {
            return { ...session /* custom fields */ };
        }),
    ],
});

Additional context

Introduced in v1.4.19 (PR #7879) which changed cookie forwarding from raw ctx.setHeader("set-cookie", ...) to a parse-and-reconstruct approach via parseSetCookieHeader + ctx.setCookie. The v1.4.5 raw pass-through approach did not have this issue.

Fix: either spread remaining parsed attributes (...rest) into the ctx.setCookie call, or revert to raw Set-Cookie header forwarding via getSetCookie().

Originally created by @dumptyd on GitHub (Apr 17, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/9231 ### Reproduction 1. Set `defaultCookieAttributes: { partitioned: true, sameSite: "none", secure: true }` and use `customSession` plugin 2. Sign in (cookie is correctly `Partitioned`) 3. Wait for `updateAge` to elapse, call `/get-session` -- session refresh re-sets cookie **without** `Partitioned` 4. Browser now has two same-name cookies (one partitioned, one not) 5. Sign out -- `expireCookie` correctly clears the partitioned cookie but the unpartitioned one survives (or vice versa) 6. Sign in again -- new partitioned cookie is set, but the ghost unpartitioned cookie from step 3 still exists 7. `/get-session` receives both cookies, picks the wrong one, returns `null` Root cause in `custom-session/index.ts` L112-124 -- the `ctx.setCookie` reconstruction explicitly maps known attributes but omits `partitioned`: ```typescript ctx.setCookie(name, attrs.value, { maxAge: attrs["max-age"], expires: attrs.expires, domain: attrs.domain, path: attrs.path, secure: attrs.secure, httpOnly: attrs.httponly, sameSite: attrs.samesite, // partitioned not forwarded }); ``` `parseSetCookieHeader` correctly captures `partitioned` via the default switch case, but the reconstruction ignores it. ### Current vs. Expected behavior Session breaks after refresh + re-login cycle when using partitioned cookies. All cookie attributes -- including `Partitioned` -- should be preserved when forwarding Set-Cookie headers. ### What version of Better Auth are you using? 1.4.22 (also confirmed unfixed on `main` and v1.6.5) ### System info N/A (code-level omission, not environment-specific) ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript export const auth = betterAuth({ advanced: { defaultCookieAttributes: { partitioned: true, sameSite: "none", secure: true, httpOnly: true, }, crossSubDomainCookies: { enabled: true }, }, plugins: [ customSession(async (session) => { return { ...session /* custom fields */ }; }), ], }); ``` ### Additional context Introduced in v1.4.19 (PR #7879) which changed cookie forwarding from raw `ctx.setHeader("set-cookie", ...)` to a parse-and-reconstruct approach via `parseSetCookieHeader` + `ctx.setCookie`. The v1.4.5 raw pass-through approach did not have this issue. Fix: either spread remaining parsed attributes (`...rest`) into the `ctx.setCookie` call, or revert to raw Set-Cookie header forwarding via `getSetCookie()`.
GiteaMirror added the core label 2026-04-17 20:03:48 -05:00
Author
Owner

@bytaesu commented on GitHub (Apr 17, 2026):

Hi @dumptyd, let me check 🧐

<!-- gh-comment-id:4270327009 --> @bytaesu commented on GitHub (Apr 17, 2026): Hi @dumptyd, let me check 🧐
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28641