Session Cookie Management#
Session cookie management in better-auth covers the full lifecycle: minting cookies on sign-in, caching session data client-side, forwarding Set-Cookie headers through plugins, and expiring or deleting cookies when sessions become invalid or are revoked.
Primary source files:
packages/better-auth/src/cookies/index.ts— all cookie read/write primitivespackages/better-auth/src/api/routes/session.ts—getSessionendpoint, orchestrates cache validation and DB fallbackpackages/better-auth/src/plugins/custom-session/index.ts— overrides/get-sessionand must forward innerSet-Cookieheaders correctlypackages/better-auth/src/plugins/anonymous/index.ts— after hook that interacts with session cookies on sign-in/sign-up
Cookie Types#
getCookies() defines four named cookies:
| Name | Purpose | Default maxAge |
|---|---|---|
session_token | Signed opaque reference to a DB session row | session.expiresIn (default 7 days) |
session_data | Signed/encrypted cookie cache of session+user payload | session.cookieCache.maxAge (default 5 min) |
account_data | Cached account identity (when storeAccountCookie is on) | same as session_data |
dont_remember_token | Signals session-length-only (no persistent maxAge) | session-length |
All cookies default to HttpOnly, SameSite=Lax, Path=/, and Secure when baseURL uses HTTPS or NODE_ENV=production . The __Secure- prefix is added automatically when secure cookies are enabled .
Setting Session Cookies#
setSessionCookie(ctx, session, dontRememberMe?, overrides?) is called after any successful authentication event. It:
- Reads the
dont_remember_tokencookie to determine whether a persistentmaxAgeshould be set . - Writes the signed
session_tokencookie viactx.setSignedCookie. - If
dontRememberMe, also writes thedont_remember_tokencookie. - Calls
setCookieCache()to write thesession_datacookie.
setCookieCache() encodes session+user data using one of three strategies (configurable via session.cookieCache.strategy):
compact(default) —base64url(JSON) + HMAC-SHA256signaturejwt— HS256-signed JWTjwe— AES-encrypted JWT (A256CBC-HS512 + HKDF)
Large payloads are automatically chunked into session_data.0, session_data.1, … by createSessionStore .
Session Validation and Cookie Cache in getSession#
When /get-session is called , the flow is:
- Read
session_token— if absent, returnnullimmediately. - Read
session_datacache — attempt to decode per the configured strategy.- Decode failure →
expireCookie(ctx, sessionData)and fall through to DB. - Token mismatch or version mismatch →
expireCookie. - Cache expired →
expireCookieand fall through. - Cache hit and fresh → return cached payload, optionally refreshing the cache window if
timeUntilExpiry < updateAge.
- Decode failure →
- DB lookup —
findSession(sessionCookieToken).- Session missing or
expiresAt < now→deleteSessionCookie(ctx)and returnnull. - Session update failure (concurrent deletion) →
deleteSessionCookie+UNAUTHORIZED.
- Session missing or
- On a fresh valid DB session,
setSessionCookie/setCookieCacheis called to refresh the cookie'smaxAge.
Expiring and Deleting Cookies#
expireCookie(ctx, cookie)#
expireCookie() is the atomic primitive:
- Calls
removeSetCookieEntries()to scrub any priorSet-Cookieheaders for the same name (including chunked variantsname.0,name.1, …) from both the local middleware scope and the outer endpoint scope'sresponseHeaders. - Appends a new
Set-CookiewithmaxAge: 0to force client-side deletion.
The scrub step is a security requirement: without it, a valid cookie value set earlier in the same request would still be visible in raw response headers (proxy logs, server-side SDK consumers, observability tools) even though the browser would honour the later expiry entry .
deleteSessionCookie(ctx)#
deleteSessionCookie() is the comprehensive teardown used on sign-out, session expiry, and revocation:
- Expires
session_tokenandsession_data - Expires
account_dataand cleans up chunked account fragments ifstoreAccountCookieis enabled - Expires
oauth_stateif the OAuth state strategy is"cookie" - Cleans up all
session_datachunks viacreateSessionStore().clean() - Expires
dont_remember_tokenunlessskipDontRememberMeis passed
customSession Plugin: Forwarding Set-Cookie Headers#
The customSession plugin overrides /get-session to allow a user-defined session shape. It calls the inner getSession() with returnHeaders: true, then must forward any Set-Cookie headers the inner handler set (e.g., a refreshed session_data cookie).
The plugin uses session.headers.getSetCookie() (not headers.get("set-cookie")) to iterate individual cookies without comma-joining them. Each entry is parsed via parseSetCookieHeader and re-emitted via ctx.setCookie(), then the original set-cookie header is deleted before copying remaining response headers .
Using headers.get("set-cookie") would collapse multiple Set-Cookie values into a comma-joined string, merging their attributes — causing session_token's 7-day maxAge to be overwritten by session_data's 5-minute maxAge and resulting in premature session expiry. This was fixed in PR #7879.
Anonymous Plugin: Cookie Race Condition on Sign-In#
The anonymous plugin's after hook fires on all sign-in, sign-up, and callback paths. It inspects the response's set-cookie header to determine whether a new session is being established.
Known issue: If a request carries a stale anonymous session cookie and a new session is set (triggering the after hook), the hook reads the incoming session cookie. However, expireCookie scrubs the Set-Cookie header before appending the expiry entry. If the after hook's logic calls expireCookie on session_token (e.g., during account linking cleanup or an error path) after the new session cookie was already queued, the pending Set-Cookie for the new session is removed before the deletion header is added — resulting in the new session cookie being silently dropped from the response. The hook detects the presence of a new session via parseSetCookieHeader on the raw set-cookie header ; any call to expireCookie for the same cookie name that runs before this check scrubs the entry and the hook sees no new session.
Utilities for Edge Middleware#
getSessionCookie(req, config?) and getCookieCache(req, config?) are exported from better-auth/cookies for use in edge middleware (e.g., Next.js middleware) where a full DB-backed session validation is unavailable. getSessionCookie synchronously reads the raw token; getCookieCache asynchronously decodes, verifies, and returns the cached session payload without a DB call. Both handle chunked cookies and the __Secure- prefix transparently .