Back to Knowledge
Updated Sep 2026

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."

, and then... this. The first means the provider doesn't trust your callback URL

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."

. The second means your app never found its environment variables

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."

. Both are configuration, not code. Here's how to fix them for good.

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."

isn't "get a key and go." It's a handshake between five places. Miss one, and the whole thing fails, often with an error that points somewhere else.

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)

1

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.

2

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.

3

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 secret

With 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.

4

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 too

Then redeploy

Env var changes only apply to new deployments. Push a commit or hit Redeploy.

5

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 www

GitHub → Authorization callback URL

http://localhost:3000/api/auth/callback/github
https://yourdomain.com/api/auth/callback/github

The 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

  1. Click your sign-in button on the deployed site.
  2. Look at the provider URL in the address bar. Is client_id= a real value?
  3. Run vercel env ls production and 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."

gets a new random URL, and you can't pre-register URLs that don't exist yet. Two sane options:

  • • 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 set AUTH_REDIRECT_PROXY_URL=https://auth.yourdomain.com/api/auth on both the stable and preview environments. They must share the same AUTH_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

1

User clicks "Sign in"

Your app → provider, with client_id + redirect_uri

2

Provider asks "Allow?"

User approves

3

Provider redirects back

To your callback URL, with a one-time code

4

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."