Skip to content

Cookie, Session, And Token Reference

Seamless Auth uses signed browser cookies at the app boundary and bearer tokens at the auth API boundary.

This page explains which cookies exist, what they carry, when sessions are created or rotated, and which token types are currently used by seamless-auth-api, @seamless-auth/express, and @seamless-auth/core.


Browser
-> signed HttpOnly cookies on your app/API domain
-> your backend with @seamless-auth/express
-> bearer tokens forwarded to seamless-auth-api

The browser should not need to read Seamless Auth tokens directly. The Express adapter signs local cookies with cookieSecret, then uses the token values inside those cookies when it forwards requests to seamless-auth-api.


createSeamlessAuthServer(...) defaults to these cookie names.

CookieDefault namePurpose
Access cookieseamless-accessAuthenticated app/API session
Refresh cookieseamless-refreshOpaque refresh-token storage for session rotation
Registration cookieseamless-ephemeralIn-progress registration, OTP, and passkey registration
Pre-auth cookieseamless-ephemeralIn-progress login, OTP, magic-link, and passkey login

Registration and pre-auth use the same default cookie name. You can override them separately if your application needs distinct cookies.

createSeamlessAuthServer({
authServerUrl: process.env.AUTH_SERVER_URL!,
cookieSecret: process.env.COOKIE_SIGNING_KEY!,
serviceSecret: process.env.SERVICE_SECRET!,
issuer: process.env.JWT_ISSUER!,
audience: process.env.JWT_AUDIENCE!,
accessCookieName: 'sa_access',
refreshCookieName: 'sa_refresh',
registrationCookieName: 'sa_registration',
preAuthCookieName: 'sa_pre_auth',
})

The Express adapter sets cookies as:

AttributeCurrent behavior
httpOnlyAlways true
path/
domaincookieDomain option, if provided
maxAgeToken TTL returned by the auth API
securetrue when NODE_ENV=production
sameSitenone in production, lax outside production

For cross-site browser deployments, use HTTPS and set a cookie domain that matches the frontend/API shape you are deploying.


The access cookie is a local HS256 JWT signed by the Express adapter with cookieSecret.

It stores enough information for the adapter to forward authenticated requests and for your backend to perform route checks.

FieldMeaning
subUser ID
sessionIdCurrent auth API session ID
tokenRS256 access token issued by seamless-auth-api
rolesUser global roles
emailUser email
phoneUser phone
organizationIdActive organization ID, when selected

requireAuth({ cookieSecret }) verifies this signed cookie and attaches req.user.

app.get('/api/me', requireAuth({ cookieSecret }), (req, res) => {
res.json({ user: req.user })
})

req.user contains:

type SeamlessAuthUser = {
id: string
sub: string
roles: string[]
email: string
phone: string
iat?: number
exp?: number
}

requireAuth(...) does not call the auth API and does not rotate refresh tokens. It only verifies the local access cookie. Route-specific refresh behavior happens in the adapter middleware mounted inside createSeamlessAuthServer(...).


The refresh cookie is also a local HS256 JWT signed with cookieSecret.

It contains:

FieldMeaning
subUser ID
refreshTokenOpaque refresh token issued by seamless-auth-api

The opaque refresh token is stored server-side as:

  • refreshTokenHash: bcrypt hash
  • refreshTokenLookup: HMAC lookup fingerprint derived from REFRESH_TOKEN_LOOKUP_SECRET or API_SERVICE_TOKEN

The raw refresh token is not stored in the database.


Registration and login flows use short-lived ephemeral cookies before a full access session exists.

FlowCookieCarries
Start registrationregistration cookiesub, ephemeral token
Verify registration OTPregistration cookieforwarded bearer identity
Register passkeyregistration cookieforwarded bearer identity
Start loginpre-auth cookiesub, ephemeral token
Login OTP/magic link/passkey continuationpre-auth cookieforwarded bearer identity

The auth API signs ephemeral tokens as RS256 JWTs with typ: "ephemeral" and a five-minute expiration.


TokenFormatUsed for
Access tokenRS256 JWTAuthenticated auth API requests
Ephemeral tokenRS256 JWTIn-progress login/registration steps
Refresh tokenOpaque random stringRefresh-token rotation
Service tokenAdapter-created tokenTrusted service-to-service refresh and delivery calls

Access tokens include:

ClaimMeaning
subUser ID
sidSession ID
issAuth API issuer
typaccess
rolesUser roles
org_idActive organization ID, when selected

Refresh tokens are currently opaque random strings. The auth API refresh endpoint expects the raw opaque refresh token in the bearer header, not the access JWT.


The auth API stores sessions in the sessions table.

FieldMeaning
idSession ID
userIdOwning user
infraIdApp or infrastructure identifier
organizationIdActive organization for the session
modeCurrently server
refreshTokenHashBcrypt hash of the opaque refresh token
refreshTokenLookupHMAC lookup fingerprint
lastUsedAtLast refresh/use timestamp
expiresAtAbsolute session expiry
idleExpiresAtIdle session expiry
stepUpVerifiedAtLast fresh step-up verification
stepUpMethodwebauthn or totp
replacedBySessionIdRefresh rotation successor
revokedAtRevocation timestamp
revokedReasonRevocation reason

Current session records use a one-day absolute lifetime and one-day idle timeout in the auth API. Access and refresh cookie TTLs are controlled separately by system config.


On successful refresh:

  1. the adapter verifies the refresh cookie with cookieSecret
  2. the adapter sends the raw refresh token to POST /refresh
  3. the auth API finds the active session by refreshTokenLookup
  4. the auth API creates a new session record
  5. the old session gets replacedBySessionId
  6. the auth API returns a new access token and opaque refresh token
  7. the adapter sets fresh access and refresh cookies

If an already-rotated refresh token is reused, the auth API treats it as suspicious and revokes the session chain.

The core refresh helper deduplicates simultaneous refreshes briefly so parallel browser requests do not normally race each other into token reuse.


createSeamlessAuthServer(...) installs ensureCookies(...) for routes it owns under /auth.

For those adapter routes, if a required access cookie is missing or does not contain the upstream access token, and a valid refresh cookie exists, the adapter attempts refresh and sets new cookies.

This applies to adapter-owned auth routes such as:

  • /auth/users/me
  • /auth/logout
  • /auth/organizations/*
  • /auth/step-up/*
  • /auth/admin/*
  • /auth/internal/*
  • /auth/system-config/*

For your own application routes, requireAuth(...) only verifies the access cookie. If you need refresh before custom app routes, call an adapter-owned route such as /auth/users/me from the client before retrying, or implement an explicit server-side refresh strategy with @seamless-auth/core.


Current logout behavior:

RouteMeaning
DELETE /auth/logoutRevoke current session and clear auth cookies
DELETE /auth/logout/allRevoke all user sessions and clear auth cookies
GET /auth/logoutCompatibility route for all-sessions logout

Session revocation sets revokedAt and revokedReason. Admin session routes can revoke individual sessions or all sessions for a user.


seamless-auth-api signs access and ephemeral JWTs with RS256.

In development:

  • the API generates local keys under ./keys/dev
  • the active key ID is dev-main
  • /.well-known/jwks.json serves the generated dev public key

In production:

  • SEAMLESS_JWKS_ACTIVE_KID identifies the active signing key
  • SEAMLESS_JWKS_KEY_<kid>_PRIVATE contains the private key PEM for the active key
  • JWKS_PUBLIC_KEYS contains public keys served by /.well-known/jwks.json

The Express/core handlers verify signed auth API responses against the auth API JWKS before setting local cookies.


MistakeFix
Using the access JWT as the refresh bearer tokenUse the opaque refresh token from the refresh cookie payload
Expecting requireAuth(...) to refresh sessionsLet /auth adapter routes refresh, or add explicit refresh handling
Changing cookie names in the adapter but not in requireAuth(...)Pass the same cookieName to requireAuth(...)
Calling raw auth API routes from browser codeCall your backend adapter routes unless you are building a custom server adapter
Rotating JWKS private keys without publishing public keysKeep SEAMLESS_JWKS_ACTIVE_KID, private key, and JWKS_PUBLIC_KEYS aligned