Token Management
next-api-layer handles JWT token management automatically, including validation, refresh, single-flight coalescing, and optional local verification.
Overview#
The library manages these token operations:
- Validation: Checking if a token is valid (backend per-request, or local JWT verification)
- Refresh: Automatically refreshing expired tokens, coalescing concurrent refreshes
- Reuse detection: Classifying refresh failures and failing closed on token theft (RFC 9700)
Token Validation#
Every request triggers token validation through your backend's validate endpoint:
Expected Response Format#
Your backend's /auth/me endpoint should return:
Custom Response Parsing#
If your backend uses a different format, use responseMappers:
Automatic Token Refresh#
When a token is invalid or expired, the library automatically attempts to refresh it:
Refresh Flow#
- Token validation fails (expired/invalid)
- Library calls
/auth/refreshwith current token - If successful, updates cookie with new token
- Sets
x-refreshed-tokenheader for the request - Continues with the original request
Expected Refresh Response#
Custom Refresh Parsing#
Concurrent Refresh (Single-Flight)#
When several requests arrive at once with the same expired token (navigation +
SWR polling + RSC fetches), the proxy coalesces their refreshes into a single
auth/refresh call and shares the result:
This matters for backends that rotate jti server-side: without coalescing, the
second refresh would replay a rotated token and bounce the user to login (or
orphan the session). Enabled by default (refresh.singleFlight), effective
within a single runtime instance.
Across separate serverless/edge isolates, concurrent requests may still trigger independent refreshes. Pair with idempotent refresh handling (a rotation grace window) on the backend for full coverage.
Proactive Refresh#
By default, refresh is reactive — triggered only after a 401. Enable
proactive refresh to renew a still-valid token shortly before it expires,
avoiding a guaranteed failed roundtrip on the next cycle:
Proactive refresh is best-effort: if it fails, the still-valid token keeps working. A detected token reuse remains terminal.
Reuse Detection & Fail-Closed (RFC 9700)#
A failed refresh is classified so you can react to token theft:
| reason | Meaning | Default trigger |
|---|---|---|
expired | Token past its lifetime | 401 |
revoked | Server rejected the token | 403 |
reuse | A rotated (old) token was replayed | 409 or body { code: 'token_reuse' } |
network | Transport error | fetch threw |
unknown | Anything else | — |
On reuse — or when access.guestFallbackOnUserRefreshFail: false — the proxy
clears every auth cookie and redirects to login (or 401 for API routes),
emitting auth:reuse / auth:refresh:fail audit events. It never downgrades
to a guest session.
Local Validation (Stateless)#
By default the proxy calls auth/me on every request. Switch to local
verification to validate the JWT in-process and remove the per-request backend
roundtrip:
revalidateInterval preserves revocation / allowlist enforcement: the backend
is consulted at most once every N seconds (tracked via the non-sensitive
__nal_rv cookie). Use validate.verify for RS256 / JWKS.
Dual-Token Mode#
For the OAuth2 access/refresh split — a short-lived access token plus a separate,
long-lived refresh token — name a refresh cookie:
The access token is validated on each request; only the refresh cookie is sent
to auth/refresh, which rotates both tokens. Even if the access token leaks
in a log or proxy, it is short-lived.
Token Types#
The library supports different token types:
Type Checking#
Guest vs User Tokens#
| Feature | Guest Token | User Token |
|---|---|---|
| Created automatically | Yes | No (login required) |
| Cookie name | guestToken | userToken |
| Typical TTL | 1 hour | 7 days |
| Can access protected routes | No | Yes |
Cookie Configuration#
Control how tokens are stored:
Security Best Practices#
- Always use
httpOnly: trueto prevent XSS token theft - Use
secure: truein production for HTTPS-only sameSite: 'lax'provides good CSRF protection while allowing normal navigation- Set appropriate
maxAgebased on your security requirements
Accessing Tokens#
In Route Handlers#
With API Client#
The API client handles token retrieval automatically:
Token Expiration Handling#
Proxy Level#
The proxy handles expiration automatically:
- Detects expired token from validation response
- Attempts refresh
- If refresh fails, clears cookies and redirects to login (for protected routes)
Client Level#
The AuthProvider keeps client state in sync:
Debugging#
Enable audit logging to track token operations:
This helps identify:
- Frequent refresh attempts (might indicate short token TTL)
- Failed validations (might indicate backend issues)
- Unauthorized access attempts