Production Checklist
Items to tick before sending traffic.
Run through this before your first production deploy.
Secrets
-
JWT_SECRETis at least 256 bits, in a secrets manager, not in.envchecked to git. - Refresh-token signing material is separate from
JWT_SECRET(or use the same with secret rotation in mind). - Rotation plan documented: how to roll the secret without logging everyone out simultaneously.
- OAuth client secrets (Google, Facebook, Apple, GitHub) live in the secrets manager, not in code.
-
adminConsole.secretKeyis set if the admin console is enabled, and rotated when an operator leaves.
TLS
- Backend serves HTTPS only.
- HSTS header enabled for the auth host.
-
cookieOptions.secure: true(the default) — never serve auth cookies over plain HTTP.
Cookies
For cookie-mode sessions:
-
sameSite: 'lax'(default) — strict if your app never embeds itself, none if cross-site is intentional. -
httpOnly: true(default). -
domainis explicitly set in production (don't rely on the request host). - CORS
Access-Control-Allow-Credentials: trueand explicit origin allowlist (no*).
CORS
- Required headers in
allowedHeaders:Content-Type,Authorization,x-access-token-type, the configuredtrustDeviceHeaderName(defaultnest_auth_device_trust). - Preflight (
OPTIONS) responses cached withAccess-Control-Max-Age. - If using cookie mode: explicit
originallowlist;credentials: true.
See CORS & Security.
Rate limiting
-
@nestjs/throttler(or equivalent) on/auth/login,/auth/passwordless/send,/auth/forgot-password,/auth/mfa/challenge. - Per-IP and per-identifier (so an attacker hitting many usernames from one IP is blocked).
See Rate Limiting.
Sessions
-
session.storageTypeis notMEMORY— pick DATABASE or REDIS. - If multi-instance: REDIS (or sticky sessions if you really must, but DATABASE is cleaner).
-
session.maxSessionsPerUserset to a reasonable cap (default 10). - Periodic cleanup of expired DB sessions if using
DATABASE. - Refresh tokens rotated on every refresh (the default).
MFA
-
mfa.required: truefor sensitive endpoints (or for all logins in compliance-heavy apps). - Recovery codes documented in your user-facing UX.
- Trusted-device duration matches your policy (
trustedDeviceDuration: '14d'or shorter).
Email & SMS
- All six lifecycle events have a listener that delivers (or queues) the message.
- No plaintext codes in logs, error reports, or third-party tools.
- Async delivery via queue, not inline
await, so a slow provider can't DoS your auth flow.
See Sending Emails / Sending SMS.
Audit
-
audit.enabled: true. -
audit.onEventwrites to your durable audit store (not just logs). - Tamper-evident logging if your compliance regime requires it.
Database
-
synchronize: false— schema managed by migrations. - Indexes on
nest_auth_users.email,nest_auth_users.phone,nest_auth_sessions.userId,nest_auth_identities.(provider, providerId). - CASCADE delete from
NestAuthUseris configured (it ships that way; verify it survived your migration). - Backups include all 14 auth tables.
Application
-
cookieParser()middleware is registered before routes. -
ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })is registered globally. -
AuthExceptionFilteris registered globally so error responses are structured. -
EventEmitterModule.forRoot()is inAppModuleimports — without it, every event silently no-ops. - Helmet / compression configured.
See Setup Checklist.
Monitoring
- Auth error rate dashboard.
- Login latency p95 / p99 dashboard.
- Alert on
INVALID_CREDENTIALSrate spikes (credential-stuffing). - Alert on session-store error rate.
Incident response
- Documented runbook for "leaked JWT secret" (rotate secret → all sessions invalid).
- Documented runbook for "compromised admin user" (force logout-all → reset MFA → audit).
- Tested ability to disable auth methods quickly (e.g. flip
emailAuth.enabled: falseif a CVE hits an OAuth provider).