HomeGuidesAUTH_SECRET and NEXTAUTH_SECRET: set and rotate
Guide
AUTH_SECRET and NEXTAUTH_SECRET: set and rotate
Auth.js needs AUTH_SECRET (NEXTAUTH_SECRET in older NextAuth) on the server only. What it encrypts, how to generate a strong value, and how to rotate it safely.
Independent notes from VibeLocked. Not a pentest, and not affiliated with the tools named below.
AI tools like Cursor, v0, Bolt, and Lovable often wire up NextAuth or Auth.js and drop AUTH_SECRET into .env. The next slip is adding NEXT_PUBLIC_ so the build "sees" it, or committing .env to GitHub. That secret is not like a Clerk publishable pk_ key. It is the key that protects your session cookies.
Checked against the Auth.js and NextAuth docs linked below on October 15, 2026.
The short answer
| Value | Looks like | Where it goes | If it leaks |
|---|---|---|---|
AUTH_SECRET / NEXTAUTH_SECRET |
Long random string (32+ chars) | Server only | Rotate it |
| OAuth client ID | Provider-specific id | Often public-ish (depends on provider) | Usually low urgency; still avoid shipping secrets |
| OAuth client secret | Provider secret | Server only | Rotate at the provider |
Clerk gives you a public pk_ and a secret sk_. Auth.js gives you one app secret for cookies and JWTs. Treat AUTH_SECRET like a password for your sessions, not like a publishable frontend key. See Clerk publishable key vs secret key for that contrast.
What AUTH_SECRET does
Auth.js requires AUTH_SECRET: a cryptographically secure random string, at least 32 characters. The deployment docs say it encrypts cookies and tokens, and that it is used to encode the JWT. The JWT module says Auth.js JWTs are encrypted by default with A256CBC-HS512 (a JWE), using AUTH_SECRET or the secret option you pass in config.
If someone has your AUTH_SECRET, they can decrypt or mint session material your app will trust. Rotating an OAuth client secret alone is not enough after an env leak or server compromise. The session secret can still be used to forge cookies. For one public write-up of that pattern after env theft, see minting NextAuth / Auth.js cookies.
Missing AUTH_SECRET in production is not a soft warning. Auth.js expects it; production without a secret throws.
Correct env names
Auth.js (v5 and current docs): prefer AUTH_SECRET.
AUTH_SECRET=your-long-random-string-here
# Optional provider inference examples (server only):
# AUTH_GITHUB_ID=...
# AUTH_GITHUB_SECRET=...
Older NextAuth.js: NEXTAUTH_SECRET was the common name. It still shows up in the NextAuth options docs (openssl rand -base64 32 is mentioned there). Prefer migrating to AUTH_SECRET when you upgrade.
Never create any of these:
NEXT_PUBLIC_AUTH_SECRETNEXT_PUBLIC_NEXTAUTH_SECRETVITE_AUTH_SECRETVITE_NEXTAUTH_SECRET
Those prefixes put the value in the browser bundle. Our keep secrets out of frontend code guide covers why.
Preview deploys: if you use AUTH_REDIRECT_PROXY_URL so OAuth works on preview URLs, the deployment docs say AUTH_SECRET must be the same on the stable deployment and the preview deployments that need OAuth.
Separately, do not pipe a raw callbackUrl or redirectTo from the query string into sign-in. Auth.js restricts same-host redirects by default, but custom code and loose redirect callbacks still create open redirects. See fix open redirects in Auth.js, Supabase, and Clerk.
If you use the Credentials provider, Auth.js leaves rate limiting to you. Login brute force and credential stuffing protection shows a per-IP and per-account limiter inside authorize().
Wrong patterns vibe apps hit
- AI scaffolding adds
AUTH_SECRET, then someone prefixesNEXT_PUBLIC_to fix an "undefined" error in a client component. .envwithAUTH_SECRETorNEXTAUTH_SECRETgets committed. Deleting the file in a later commit does not remove it from history.- The secret is copied into a shared helper that a
'use client'file imports. - Production and preview get different secrets while using the redirect proxy, so OAuth or sessions break in confusing ways.
Keep the secret in host env settings (Vercel, Netlify, Replit Secrets, and so on), read it only in server code, and keep .env gitignored. More habits for Cursor, Bolt, and Lovable live in the environment variables guide.
How to generate a good secret
From the Auth.js deployment docs, either:
npm exec auth secret
or:
openssl rand -base64 33
(The older NextAuth options page mentions openssl rand -base64 32. Either length meets the "at least 32 characters" bar when base64-encoded this way; prefer the Auth.js deployment guidance for new projects.)
Put the value in your host's secret store and in a local gitignored .env. Do not paste it into a chat with an AI coding tool, and do not commit it.
How to check for a leak
- Search the repo (including history) for
AUTH_SECRET,NEXTAUTH_SECRET,NEXT_PUBLIC_AUTH_SECRET, andVITE_NEXTAUTH_SECRET. - Confirm
.envis not tracked:git ls-files | grep -i env. - Check your host for any public-prefixed copy of the secret.
- Run the VibeLocked scanner on a public GitHub repo for related patterned keys (OAuth client secrets, Stripe, Clerk, and so on).
AUTH_SECRETitself is a random string, so scanners may not flag the value the way they flagsk_live_keys. Variable-name search still matters.
If the secret was in a public place, rotate it. Order of operations is in the first hour after an API key leaks. Cleaning git history is optional cleanup after rotation; see remove a leaked key from GitHub history.
How to rotate
Option A: zero-downtime with a secret array
The Auth.js JWT decode params say you can pass secret as an array. The first secret that successfully decrypts the JWT is used. Put the newer secret at the start of the array so new sessions use it, and keep the old one so existing sessions still decrypt:
- Generate a new secret.
- In your Auth.js config (encode/decode path that accepts
secret), setsecret: [newSecret, oldSecret]. - Update host env so the new value is available to that config, redeploy, and confirm sign-in still works for new and old sessions.
- After your JWT
maxAgewindow passes (Auth.js default is often 30 days:30 * 24 * 60 * 60seconds), drop the old secret and keep only the new one.
Option B: swap and accept logout
- Generate a new secret.
- Set
AUTH_SECRET(orNEXTAUTH_SECRET) to only the new value on the host. - Redeploy. Existing encrypted sessions will fail to decrypt, so users sign in again.
- Remove the old value from every env store and from any committed files, then rotate related OAuth client secrets if those also leaked.
Also rotate OAuth client secrets at the provider if they were in the same leaked .env. Session forgery risk is why the app secret matters even after client secrets change.
Checklist
- Production has
AUTH_SECRETset (prefer that name overNEXTAUTH_SECRET) - No
NEXT_PUBLIC_orVITE_prefix on the auth secret -
.envis gitignored and not in history for real secrets - Secret is generated with
npm exec auth secretoropenssl rand -base64 33(or equivalent strength) - Preview + stable share the same
AUTH_SECRETwhen usingAUTH_REDIRECT_PROXY_URL - A leak triggered rotation (array
[new, old]or hard swap) and host env was updated before celebrating - Old secret removed after
maxAgeif you used the array method
Sources
Questions
Is AUTH_SECRET the same as NEXTAUTH_SECRET?
Mostly yes. Auth.js prefers AUTH_SECRET. Older NextAuth.js used NEXTAUTH_SECRET. AUTH_SECRET is the preferred name going forward and works as the alias for the same job: encrypting cookies and JWTs on the server.
Can I put AUTH_SECRET behind NEXT_PUBLIC_ or VITE_?
No. Those prefixes put the value into the JavaScript every visitor downloads. AUTH_SECRET and NEXTAUTH_SECRET must stay server only, with no public prefix.
Do I have to log everyone out when I rotate AUTH_SECRET?
Not if you use the Auth.js JWT secret as an array. Put the new secret first and the old one second so existing sessions still decrypt. After your JWT maxAge window (default often 30 days), drop the old secret. Or set only the new AUTH_SECRET and accept that current sessions stop working.
Will the VibeLocked scanner find a leaked AUTH_SECRET?
Often not by value alone. AUTH_SECRET is a random string, not a prefixed key like sk_live_. Search your repo and host env for the variable names AUTH_SECRET and NEXTAUTH_SECRET, and for a committed .env. The scanner still helps for related OAuth client secrets and other patterned keys.