CORS & Security
Headers, CSRF, Helmet, and CSP for auth-mode browsers.
The auth flow uses two custom request headers and (in cookie mode) browser cookies. CORS misconfigurations are the #1 cause of "auto-refresh isn't working" tickets.
Required CORS headers
Whatever CORS solution you use (@nestjs/common's enableCors, cors middleware, a reverse proxy), your allowedHeaders must include:
| Header | Why |
|---|---|
Content-Type | Standard JSON request bodies |
Authorization | Bearer tokens in header mode |
x-access-token-type | Auto-detect signal between header and cookie mode |
nest_auth_device_trust (or your trustDeviceHeaderName) | Trusted-device tokens for MFA |
Plus any custom headers you've added (tracing, app-version).
Cookie mode + CORS
If accessTokenType: 'cookie':
credentials: trueis mandatory (server side).origin: '*'is forbidden — pick explicit origins.- The frontend's HTTP layer must send
credentials: 'include'. The library'sFetchAdapterdoes this automatically when in cookie mode.
If any of these are wrong, the browser silently strips the cookie and you'll see auth fail with no useful console message.
CSRF posture
In header mode (Bearer), CSRF is not an issue — no cookie is auto-attached, so cross-site requests can't piggyback your auth.
In cookie mode, the auth cookies are HttpOnly (the default cookieOptions) and sameSite: 'lax' (default). This prevents CSRF for top-level navigations, but POST forms from another origin can still attach the cookie if sameSite: 'lax'. Two layers of defense:
- Stick with
sameSite: 'lax'orstrict— nevernoneunless you genuinely need cross-site auth. - Add a CSRF token for state-changing endpoints if your threat model warrants. The library doesn't ship one — use
csurfor roll your own.
Helmet
Recommended headers via Helmet:
Why both Bearer and cookie auth in Swagger
When accessTokenType: null (auto-detect), the /auth/* endpoints accept either. Register both security schemes in Swagger so the docs let you "Try it out" with either:
Trusted-device header collisions
trustDeviceHeaderName defaults to nest_auth_device_trust. If that name conflicts with another header in your stack, change it via mfa.trustDeviceStorageName and update your CORS allowlist accordingly. See the custom-trusted-device-header recipe.
TLS
- Enforce HTTPS at the load balancer.
- Set
cookieOptions.secure: true— never serve auth cookies over HTTP. - HSTS preload list submission for production hosts.