Cookie transport stores access and refresh credentials in httpOnly cookies. JavaScript cannot
read those credentials, but the browser sends them automatically; unsafe methods therefore require
CSRF protection.
Configure cookie mode
transport: transports.cookie({
secure: true,
sameSite: 'lax',
accessCookie: 'jwt_access',
refreshCookie: 'jwt_refresh',
csrfCookie: 'jwt_csrf',
csrfHeader: 'x-csrf-token',
accessPath: '/',
refreshPath: '/auth/refresh',
})SameSite is restricted to lax or strict. Keep secure: true outside local HTTP tests. The
refresh cookie is scoped to the refresh route, reducing where the long-lived credential is sent.
The package writes the CSRF cookie without httpOnly so browser code can echo it.
Send the CSRF proof
For POST, PUT, PATCH, and DELETE, read the jwt_csrf cookie and copy its exact value into
x-csrf-token:
await fetch('/auth/refresh', {
method: 'POST',
credentials: 'include',
headers: { 'x-csrf-token': readCookie('jwt_csrf') },
})The guard compares the cookie and header with a timing-safe equality check. Missing or mismatched
proof raises E_CSRF_TOKEN_MISMATCH. GET, HEAD, and OPTIONS do not require the header.
Deployment checklist
- Terminate TLS before the application and leave
secure: true. - Use one canonical first-party origin when possible.
- Set
domainonly when trusted sibling subdomains must share authentication. - Do not widen the refresh cookie path beyond the refresh endpoint.
- Keep state-changing routes on unsafe HTTP methods; never mutate state through
GET. - If
@adonisjs/shieldalso performs CSRF validation, align the exempt routes and header name so both layers enforce the same browser contract.
Logout clears all three cookies with the same path, domain, SameSite, and secure attributes used
when writing them.