The OAuth Trap
Why "Sign in with Google" works on localhost and explodes in production. The 5-part setup that trips up every vibe coder, with Auth.js v5 on Next.js 16 as the example.
1The Two Dreaded Errors
Error 400: redirect_uri_mismatch
The redirect URI in the request does not match the ones authorized for the OAuth client.
github.com/login/oauth/authorize?client_id=undefined
The provider has no idea which app you are. Login dead-ends.
You followed the tutorial, copied the keys, deployed to Vercel Vercel A cloud platform built by the team behind Next.js. Push to Git and every branch gets a live Preview Deployment; production is one merge away. Functions run on Fluid Compute by default, and Vercel adds storage, AI Gateway, queues, sandboxes, and more. "Like magic website publishing. Push to GitHub, and boom — it's live." Callback URL (Redirect URI) The exact URL where OAuth providers send users after login. Must match EXACTLY in both your app and the provider's console — including localhost vs production, port numbers, and trailing slashes. "Like giving a hotel the exact address to send your luggage. Wrong address = luggage never arrives. Wrong callback URL = 'redirect_uri_mismatch' error." Environment Variable A secret value stored outside your code, like API keys or passwords. Keeps sensitive info out of your codebase. "Like a sticky note with the WiFi password — you know it, but you don't write it on the wall."
2The 5 Places You Must Configure
OAuth OAuth A secure way to log in using another account (like Google or GitHub) without sharing your password with the app. "Like a hotel key card. The front desk (Google) vouches for you, so the room (app) lets you in."
1. Provider console
Create the OAuth app
2. Credentials
Client ID + Secret
3. .env.local
Local secrets, exact names
4. Vercel
Production + Preview env
5. Callback URLs
The trap!
3Step by Step (Google, with GitHub Notes)
Create the OAuth client
Google: Google Cloud Console → APIs & Services → Credentials → Create credentials → OAuth client ID → type Web application. Configure the consent screen first if prompted.
GitHub: Settings → Developer settings → OAuth Apps → New OAuth App.
Grab the Client ID and Secret
Client ID: 123456789-abc123.apps.googleusercontent.com
Client Secret: GOCSPX-xxxxxxxxxxxxxxxx
Treat the secret like a password. If it lands in a chat, a screenshot, or a commit, regenerate it.
Add them to .env.local with the exact names
.env.local
# Auth.js v5 reads these automatically
AUTH_SECRET=run-npx-auth-secret-to-generate
AUTH_GOOGLE_ID=123456789-abc123.apps.googleusercontent.com
AUTH_GOOGLE_SECRET=GOCSPX-xxxxxxxxxxxxxxxx
AUTH_GITHUB_ID=Ov23li...
AUTH_GITHUB_SECRET=...Terminal
npx auth secretWith providers: [Google, GitHub] and no explicit clientId, Auth.js v5 looks for AUTH_{PROVIDER}_ID and AUTH_{PROVIDER}_SECRET. Any other spelling (GITHUB_ID, GITHUB_CLIENT_ID) and it quietly gets undefined.
Add the same variables to Vercel
Your .env.local never leaves your laptop. Vercel needs its own copy, in the dashboard (Project → Settings → Environment Variables) or from the terminal:
Terminal
vercel env add AUTH_SECRET production
vercel env add AUTH_GITHUB_ID production
vercel env add AUTH_GITHUB_SECRET production
# repeat with "preview" if preview deployments should log in tooThen redeploy
Env var changes only apply to new deployments. Push a commit or hit Redeploy.
Register callback URLs (THE TRAP)
This is where most vibe coders get stuck.
The provider only sends users back to URLs you've pre-approved, and it compares them exactly: scheme, host, port, path. For Auth.js in Next.js the path is always /api/auth/callback/<provider>.
Google → Authorized redirect URIs
http://localhost:3000/api/auth/callback/google
https://yourdomain.com/api/auth/callback/google
https://www.yourdomain.com/api/auth/callback/google # only if you serve on wwwGitHub → Authorization callback URL
http://localhost:3000/api/auth/callback/github
https://yourdomain.com/api/auth/callback/githubThe pattern
[scheme]://[exact-host][:port]/api/auth/callback/[provider]
4War Story: client_id=undefined
A production app shipped with "Sign in with GitHub" sending client_id=undefined. Google login worked fine. Local dev worked fine. The cause: the GitHub env vars were named for NextAuth v4 (GITHUB_ID / GITHUB_SECRET), but the app ran Auth.js v5, which auto-infers AUTH_GITHUB_ID / AUTH_GITHUB_SECRET.
No build error. No runtime crash. Just a broken redirect that only real users saw.
How to catch it in 30 seconds
- Click your sign-in button on the deployed site.
- Look at the provider URL in the address bar. Is
client_id=a real value? - Run
vercel env ls productionand compare names letter by letter with what your auth config reads.
Prevention: validate required env vars at startup (a tiny Zod schema in lib/env.ts) so a missing AUTH_GITHUB_ID fails the build, not the user. More in Environment Variables.
5The Sneaky Gotchas
www vs apex
https://yourdomain.com and https://www.yourdomain.com are different hosts to OAuth. If Vercel redirects apex → www, users actually sign in on www, so the www callback is the one that must be registered. Pick one canonical host (see Domains) and register that one, or both.
Preview deployments
Every preview deployment Preview Deployment An automatic staging environment created for every pull request or branch. Lets you see and test changes before merging to production. "Like a dress rehearsal before opening night. See exactly how it looks before going live."
- • Skip OAuth on previews and test sign-in on a stable staging domain.
- • Use Auth.js's redirect proxy: keep one stable deployment (e.g.
auth.yourdomain.com), register only its callback, and setAUTH_REDIRECT_PROXY_URL=https://auth.yourdomain.com/api/authon both the stable and preview environments. They must share the sameAUTH_SECRET.
GitHub OAuth App callback rules
GitHub OAuth Apps now accept up to 10 callback URLs, and the redirect_uri must match one of them exactly. Plenty of older apps only have a single callback URL registered, because that used to be the limit. Many teams still create separate dev and prod OAuth Apps, which keeps the production secret off laptops. If you do, dev and prod get different AUTH_GITHUB_ID values, so set them per environment.
AUTH_URL and AUTH_TRUST_HOST
On Vercel, Auth.js v5 figures out the host from the request, so you normally don't set AUTH_URL. Behind other reverse proxies you may need AUTH_TRUST_HOST=true. A stale AUTH_URL or NEXTAUTH_URL pointing at the wrong host is a classic cause of mismatch errors.
6How OAuth Actually Works
User clicks "Sign in"
Your app → provider, with client_id + redirect_uri
Provider asks "Allow?"
User approves
Provider redirects back
To your callback URL, with a one-time code
Your server swaps the code
Uses the secret, gets the profile, sets a session cookie
Step 1 breaks when client_id is missing. Step 3 breaks when the callback URL isn't on the provider's list (redirect_uri_mismatch). Step 4 breaks when the secret is wrong or missing.
7OAuth Setup Checklist
8Still Not Working?
redirect_uri_mismatch
The callback URL doesn't match exactly.
Fix: Compare character by character: http vs https, www vs apex, port (3000 vs 3001), trailing slash, provider name in the path.
client_id=undefined in the provider URL
Env var missing or misnamed in this environment.
Fix: Use AUTH_{PROVIDER}_ID / AUTH_{PROVIDER}_SECRET, add them for Production (and Preview), then redeploy.
"Server configuration" error page
Usually AUTH_SECRET is missing in production.
Fix: Generate one with npx auth secret, add it in Vercel, redeploy.
Works locally, fails in production
Production callback URL not registered, or env vars only set for Development.
Fix: Register the production callback; check vercel env ls production.
Google consent screen warnings
Your app is in Testing mode or unverified.
Fix: Add test users while in Testing; publish and complete verification before real launch.
Pro tips
- Register callbacks before you need them. New domain or port? Add it to the provider first.
- Always test the deployed site. Localhost working proves nothing about production.
- Give Claude this page. "Here's my auth.ts, my env var names from vercel env ls, and the provider URL I'm redirected to. Find the mismatch."
Related Resources
OAuth Providers Guide
Google, GitHub, Apple, X, Microsoft, and more: setup notes for each.
ReadAuthentication Playbook
Better Auth vs Auth.js vs Clerk vs Supabase Auth.
ReadEnvironment Variables
vercel env, .env.local, and the NEXT_PUBLIC_ trap.
ReadDomains
Pick www or apex before you register callbacks.
ReadSources: Auth.js deployment docs · GitHub OAuth App docs