Session management architecture
Better Auth manages sessions using a traditional cookie-based session management approach. The session is stored in a cookie and sent to the server on every request. The server verifies the session and returns user data if the session is valid. The main session_token cookie is a server-side session identifier. If session.cookieCache is enabled, a separate session_data cookie is written for short-lived cached session data, which is distinct from the JWT plugin's /token endpoint and set-auth-jwt header output.
Default session expiration
Sessions expire after 7 days by default. Whenever the session is used and the updateAge threshold is reached, the session expiration is updated to the current time plus the expiresIn value.
Session freshness concept
Some endpoints in Better Auth require the session to be fresh. A session is considered fresh if its createdAt is within the freshAge limit. The default freshAge is set to 1 day (60 * 60 * 24 seconds).
getSession client function
The getSession function retrieves the current active session from the authClient. Usage: const { data: session } = await authClient.getSession()
useSession client function
The useSession action provides a reactive way to access the current session from the authClient. Usage: const { data: session } = authClient.useSession()
listSessions client function
The listSessions function returns a list of sessions that are active for the user. Usage: const sessions = await authClient.listSessions()
revokeSession client function
The revokeSession function ends a session manually from any device the user is signed into. It takes a session token parameter. Usage: await authClient.revokeSession({ token: "session-token" })
revokeOtherSessions client function
The revokeOtherSessions function revokes all sessions except the current session. Usage: await authClient.revokeOtherSessions()
revokeSessions client function
The revokeSessions function revokes all sessions for the user. Usage: await authClient.revokeSessions()
updateSession client function limitations
The updateSession function allows updating custom additional fields on the session if they are configured. Core session fields (token, userId, expiresAt, createdAt, updatedAt, ipAddress, userAgent) cannot be updated through this endpoint. Only custom additional fields are allowed.
updateSession client usage
The updateSession function can update custom fields on the session. Example: await authClient.updateSession({ theme: "dark", language: "en" })
updateSession server usage
On the server, updateSession can be called through auth.api.updateSession with body containing the fields to update and headers containing the user's session token. Example: await auth.api.updateSession({ body: { theme: "dark" }, headers: await headers() })
Revoke sessions on password change
When changing a user's password, you can revoke all other sessions by passing revokeOtherSessions: true to the changePassword function. Usage: await authClient.changePassword({ newPassword: newPassword, currentPassword: currentPassword, revokeOtherSessions: true })
Cookie cache concept
Cookie caching stores session data in a short-lived, signed cookie similar to how JWT access tokens are used with refresh tokens. When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short maxAge ensures that session data gets refreshed regularly. If a session is revoked or expires, the cookie is invalidated automatically.
Cookie cache session revocation caveat
When cookieCache is enabled, revoked sessions may remain active on other devices until the cookie cache expires (maxAge). This is because cookie cache stores session data in the client's browser, the server cannot directly delete cookies from other devices, and sessions are only revalidated when the cache expires or disableCookieCache: true is used.
Cookie cache strategies
Better Auth supports three encoding strategies for cookie cache: compact (default, uses base64url encoding with HMAC-SHA256 signature, most compact format with no JWT spec overhead, best for performance and size), jwt (standard JWT with HMAC-SHA256 by default, signed but not encrypted, readable by anyone but tamper-proof), jwe (uses JWE with A256CBC-HS512 and HKDF key derivation, fully encrypted tokens, neither readable nor tamperable, most secure but largest size).
Cookie cache strategy comparison table
Strategy comparison: compact (smallest size, good security via signing, readable, not interoperable, best for performance-critical internal use); jwt (medium size, good security via signing, readable, interoperable, best for JWT compatibility and external integrations); jwe (largest size, best security via encryption, not readable, interoperable, best for sensitive data and maximum security).
JWKS-backed cookie-cache JWTs
If you want the session_data cookie to be verifiable with the JWT plugin's JWKS endpoint instead of a shared secret, enable the JWT plugin and set sessionCookieCache: true. The primary session_token cookie is still an opaque session identifier and is not exposed through JWKS. Cookie-cache JWTs use a separate token profile from the JWT plugin's /token endpoint, so tokens are not interchangeable.
Disable cookie cache for session fetch
To force the server to fetch the session from the database and refresh the cookie cache, pass disableCookieCache: true to getSession query parameters. Example: const session = await authClient.getSession({ query: { disableCookieCache: true } }). On the server: await auth.api.getSession({ query: { disableCookieCache: true }, headers: await headers() })
Secondary storage session storage
By default, if a secondary storage is provided in the auth configuration, the session will be stored in the secondary storage instead of the database.
Preserve sessions in database on revocation
When a session is revoked with secondary storage enabled, it is normally removed. Enable preserveSessionInDatabase to keep the session row in the database instead of deleting it. The preserved row is marked ended (expiresAt is set to revocation time), so it is never restored as a live session. This is useful for tracking revoked sessions.
Stateless session management
Better Auth supports stateless session management without any database. The session data is stored in a signed/encrypted cookie and the server never queries a database to validate sessions—it simply verifies the cookie signature and checks expiration.
Automatic stateless mode
If you don't pass a database configuration, Better Auth will automatically enable stateless mode.
Manual stateless mode configuration
To manually enable stateless mode, configure cookieCache with enabled: true and account with storeStateStrategy: "cookie" and storeAccountCookie: true. In stateless OAuth flows, storeAccountCookie stores provider account data including OAuth token material in an encrypted account_data cookie. getAccessToken({ useAccountCookie: true }) can refresh expired provider access tokens when the account cookie contains a refresh token.
Stateless session versioning
To invalidate all stateless sessions, change the version of the cookie cache and redeploy the application. Example: session: { cookieCache: { version: "2" } }. This will invalidate all sessions that don't match the new version.
Stateless sessions with secondary storage
You can combine stateless sessions with secondary storage (Redis, etc.) for the best of both worlds. Cookies are used for session validation (no DB queries) while Redis stores session data and refreshes the cookie cache before expiry. Sessions can be revoked from secondary storage and the cookie cache will be invalidated on refresh.
Customize session response
When calling getSession or useSession, the session response can be customized using the customSession plugin. The plugin receives user and session objects and returns a customized response object. Example: customSession(async ({ user, session }) => { const roles = findUserRoles(session.session.userId); return { roles, user: { ...user, newField: "newField" }, session }; })
customSession callback session field caveat
The session object passed to the customSession callback does not infer fields added by other plugins. As a workaround, pull up your auth options and pass them to the customSession plugin to infer the fields. Example: customSession(async ({ user, session }, ctx) => { return { user, session }; }, options)
Session caching does not include custom fields
Session caching, including secondary storage or cookie cache, does not include custom fields added by the customSession plugin. Each time the session is fetched, the custom session function will be called to add those fields.
Mutate list-device-sessions endpoint
The customSession plugin can mutate the response of the /multi-session/list-device-sessions endpoint from the multi-session plugin by passing shouldMutateListDeviceSessionsEndpoint: true as the third parameter. By default, this endpoint response is not mutated.
Account cookie storage in stateless mode
In stateless OAuth flows, oversized account cookies can be chunked by Better Auth, but browsers and proxies can still enforce total cookie or header limits. Use database-backed account storage for providers that issue large JWTs or for production flows that need durable token storage.
Multi-session signout behavior
When a user calls the signOut method, the multi-session plugin automatically revokes all active sessions for the user. You do not need to call a separate method to revoke all sessions.