Auth Redirect Returns to Wrong URL

Sign-in flow redirects to localhost or the wrong domain. Configure allowed redirect URIs.

In production, you click Google / GitHub sign-in, complete the consent screen, and the browser redirects back to http://localhost:3000/auth/callback (blank page), some unrelated domain, or the OAuth provider explicitly errors out with redirect_uri_mismatch. This is the single most common OAuth configuration bug: the redirect_uri your code sends to the provider doesn’t byte-exactly match an entry in the provider’s allowed list.

OAuth providers (Google, Auth0, Supabase, Clerk) validate redirect_uri with character-exact equality: scheme (http vs https), domain, port, path, trailing slash — any mismatch is rejected. Once you internalize that, debugging gets straightforward.

Common causes

Ordered by hit rate, highest first.

1. Production callback URL isn’t in the allowed list

Most common. You added http://localhost:3000/auth/callback during dev, deployed to prod, and forgot to add https://yourdomain.com/auth/callback.

How to spot it: Provider error page shows “redirect_uri_mismatch” and the URI it received; compare against the allowed list in the provider console.

2. Hardcoded localhost in code

const redirectUri = "http://localhost:3000/auth/callback"; // ❌ forever dev

That line never changes after deploy, so the prod request still sends localhost.

How to spot it: Search your codebase for hardcoded localhost or a literal domain.

3. Scheme or trailing-slash mismatch

https://example.com/cbhttps://example.com/cb/ (extra slash). http://https://.

How to spot it: Browser DevTools → Network → check redirect_uri= against the provider’s allowed list character by character.

4. www vs apex domain

https://www.example.com/cbhttps://example.com/cb. If your site supports both but the list only has one, requests from the other fail.

How to spot it: Does your site do www ↔ apex redirects?

5. Preview / staging URL not registered

Vercel previews use random subdomains like xxx-yyy.vercel.app. OAuth will always fail in preview unless you specifically handle it.

How to spot it: Failing only in preview deployments?

6. Provider auto-strips path or query

Some providers (especially SAML / older SSO) ignore path and match only origin. Adding ?from=signup to the redirect URI gets stripped — you land on / instead of /auth/callback.

How to spot it: Redirect lands on / rather than /auth/callback.

Shortest path to fix

Step 1: Capture the exact redirect_uri the provider received

Click sign-in, open Network tab, find the request to the provider:

https://accounts.google.com/o/oauth2/v2/auth?
  client_id=...&
  redirect_uri=https%3A%2F%2Fyourdomain.com%2Fauth%2Fcallback&
  ...

Copy the redirect_uri= value (URL-decode it). That’s the exact string the provider sees.

Step 2: Add it verbatim to the provider’s allowed list

Google Cloud Console:
  APIs & Services → Credentials → OAuth client → Authorized redirect URIs

Auth0:
  Application → Allowed Callback URLs

Supabase:
  Authentication → URL Configuration → Site URL + Redirect URLs

Clerk:
  Paths → Settings

Save. Google takes 5-10 minutes to propagate (others are instant).

Step 3: Drive base URL from env

// Next.js example
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL; // never fall back to localhost
if (!baseUrl) throw new Error("NEXT_PUBLIC_SITE_URL not set");

const redirectUri = `${baseUrl}/auth/callback`;

Set per-environment in your host platform (Vercel / Netlify / Railway):

Local (.env.local):    NEXT_PUBLIC_SITE_URL=http://localhost:3000
Preview:               NEXT_PUBLIC_SITE_URL=(dynamic — see Step 4)
Production:            NEXT_PUBLIC_SITE_URL=https://yourdomain.com

Step 4: Handle preview deployments

Each Vercel preview has a different subdomain. Two approaches:

Option A (simple, looser security):
  Allow https://*.vercel.app in the provider (if it supports wildcards)

Option B (recommended, safer):
  Use process.env.VERCEL_URL for preview:
  const baseUrl = process.env.VERCEL_URL
    ? `https://${process.env.VERCEL_URL}`
    : process.env.NEXT_PUBLIC_SITE_URL;
  Then register a fixed alias like https://your-app-git-main-team.vercel.app
  (recommended: one stable preview alias per branch)

Step 5: End-to-end verify in incognito

1. Visit production URL
2. Click sign-in
3. Network tab: verify redirect_uri value is right
4. Complete Google consent
5. Land back on your page → check cookies / session
6. Refresh → confirm session persists

If any step fails, capture console + network errors.

Step 6: Diff scheme / slash exactly

If still failing, print the provider-side URI and the code-side URI and diff them:

echo "provider: https://example.com/auth/callback"   # in the dashboard
echo "code:     https://example.com/auth/callback/"  # in the request (note slash)

One-character difference is enough to break it.

Prevention

  • Treat the allowed-redirect list as part of the deploy checklist — new env = new redirect URI
  • Ban hardcoded localhost / literal domains in code; add a CI lint rule
  • Use one env var name across all envs (NEXT_PUBLIC_SITE_URL) for consistency
  • Plan previews up front: wildcard-allow or stable alias
  • Always run a full sign-in flow on the production domain before considering deploy complete
  • Annotate allowed-list entries (env, owner, date added) for future audits
  • Periodically prune dead dev / staging URIs from the allow list — they’re security risk

Tags: #Backend #Debug #Troubleshooting