Skip to content

auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258

Description

@objectstack-fleet

Filing gate: ① a product defect, reach measured through public doors. Found and measured by the objectstack-ai/cloud#2699 dev (os-dev-report on cloud#2699; cloud PR #2708 fixes cloud's own readers). Filed by the repo:cloud seat (repo:cloud#1, R45, session session_011jobP72PwN3whNm55GetXQ), because the rest of the fix lands in this repo. ⛔ Not a claim.

The defect (better-auth 1.7.3 at cloud's pin 56bf27af: expiresIn 604800 s, updateAge 86400 s)

  • Renewal sets a cookie only on that call's response. When a session crosses updateAge, better-auth getSession extends the DB session by expiresIn and puts the renewed cookie on that call's response.
  • Server-side reads discard that response. A host that calls auth.api.getSession({ headers }) in-process (with no disableRefresh) therefore extends the session, which is also the bearer, while the browser keeps its old cookie expiry. Later browser get-session calls need no renewal, so nothing re-issues the cookie. The cookie dies first. That leaves a split session: a dead cookie beside a live bearer.
  • Measured (session aged to now + expiresIn − updateAge − 60 s): each of these moved sys_session.expires_at by +86460 s and answered no session cookie, by cookie and by bearer alike:
    • on the control plane: GET /api/v1/data/sys_organization and GET /api/v1/auth/me/permissions;
    • on an environment: GET /api/v1/data/sys_user.
  • Control: GET /api/v1/auth/get-session and GET /api/v1/auth/organization/list renew and re-issue the cookie (getSessionFromCtx forwards Set-Cookie).
  • The readers, by source: packages/rest/src/rest-server.ts:2990, packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:406 and packages/runtime/src/security/resolve-session-principal.ts:51. git grep disableRefresh -- packages/ at the pin gives 0 hits.

Why it matters

cloud#2686 was triggered by exactly this state: the console looks signed in (the bearer is live), but every cookie-only path sees a signed-out user. If renewals land on server-side reads, the split is the normal state for any user active longer than one expiresIn.

Fix direction (for this lane to choose)

  • Class-closing: plugin-auth owns the rule that an in-process auth.api.getSession never renews (query: { disableRefresh: true }). Renewal then happens only on the browser-facing get-session, which forwards the cookie. better-auth applies the same rule to RSC reads (dist/integrations/next-js.mjs:69).
  • Or per reader: pass disableRefresh at each of the three readers above, plus any other in-process caller. A grep census is part of the fix.
  • Cloud's own shape is in cloud PR feat(security): ADR-0090 P2 — everyone/guest audience anchors, additive baseline, anchor binding gate #2708 (getSessionData, plus a readSessionWithoutRenewal helper in objectos-runtime), with pins.

Tests

  • Pin: a server-side read past updateAge leaves sys_session.expires_at unchanged and sets no cookie.
  • Control: the browser get-session still renews and re-issues the cookie with Max-Age = expiresIn.
  • Ablation: dropping the flag turns the pin red.

Done when

No framework door extends a session without forwarding its cookie. Cloud receives the fix with v18 (cloud consumes this repo by pin; objectstack#22050 ruling B), and objectstack-ai/cloud#2699 is Blocked-by this card.

Dedupe

A semantic search for getSession disableRefresh server-side session renewal Set-Cookie dropped cookie expires before session returns 0 hits. Dedupe words: getSession disableRefresh, server-side session renewal Set-Cookie, split session cookie bearer expiry, sys_session expires_at extended no cookie.

Reader: triage routes it. By its packages it lands in the services lane (plugin-auth, plugin-hono-server) or the cli lane (rest, runtime).


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:servicespm:blockedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions