Skip to Content
Delmare Digital

Sessions

How Better Auth sessions slide through Payload, when a refreshed cookie is sent, and how to mount Better Auth under a custom API route.

Sessions are Better Auth sessions. The plugin validates them on every Payload request through betterAuthStrategy, and forwards refreshed cookies wherever there is a response to carry them.

Sliding sessions

Sessions slide the way Better Auth documents (session.updateAge): once the refresh window is reached, getSession() extends the session row and issues a refreshed cookie.

  • Payload REST and GraphQL requests. betterAuthStrategy forwards that Set-Cookie through Payload's responseHeaders, so the browser's cookie moves with the database.
  • No response to carry a cookie. Server components, getServerSession() and payload.auth({ headers }) read the session with disableRefresh, so the cookie and the database row can never drift apart.

To let a Local API call refresh the session, tell Payload it may set headers and forward what it returns:

app/api/whoami/route.ts
import { getPayload } from 'payload'
import config from '@payload-config'

export async function GET(req: Request) {
  const payload = await getPayload({ config })
  const { user, responseHeaders } = await payload.auth({ headers: req.headers, canSetHeaders: true })
  return Response.json({ user }, { headers: responseHeaders })
}

Since 0.13.0. Before that, the refreshed cookie was dropped, and anyone using only Payload (the admin included) was logged out after expiresIn regardless of activity.

Reading the session on the server

Use getServerSession or getServerUser in server components and route handlers. Both are read-only.

Custom API route

The plugin mounts Better Auth at routes.api + authBasePath (default /api + /auth). Better Auth's own router returns 404 for any request outside its basePath, which defaults to /api/auth. If your Payload config sets routes: { api: '/api/payload' }, tell Better Auth where it lives:

src/payload.config.ts
// inside createAuth
betterAuth({
  basePath: '/api/payload/auth', // <routes.api> + <authBasePath>
  // ...
})

basePath also sets the path embedded in emailed password-reset and magic links.

The admin components pick up routes.api automatically. At startup the plugin compares Better Auth's resolved basePath with the actual mount and logs an error naming the exact value to set if they differ.

Outside the admin, a client built with createPayloadAuthClient() defaults its base URL to window.location.origin, which Better Auth expands to <origin>/api/auth. With a custom route, pass the full mount as baseURL (for example ${window.location.origin}/api/payload/auth). Inside admin components, useAuthClientBaseURL() from @delmaredigital/payload-better-auth/components derives it for you.

Edit this page on GitHub

On this page