Symptoms / Fix guide

Why is my OAuth callback not working?

Matt Turley, Continuum. Updated November 5, 2026.

For founders whose "Sign in with Google" (or GitHub, Apple, Microsoft) works on localhost and breaks, loops or lands on the wrong page once the app is live.

The short answer

Why is my OAuth callback not working?

The provider is rejecting the redirect, or your callback cannot finish the code exchange. The reason is in the URL (look for error and error_description) or the provider console. The usual causes are a redirect URI that does not match exactly, a localhost Site URL left in production, or a client secret that differs between environments.

What is really going on

OAuth is a round trip. Your app sends the user to the provider with a client id and a redirect URI; the provider authenticates them and sends the browser back to that exact URI with a one-time code; your server then exchanges the code for a session using a client secret. A fault at any of those hand-offs ends with the user stuck on a provider error page, back on the login page, or on a blank callback route.

Almost every production failure is a configuration mismatch between the app and the provider, because the two are set up in different consoles and tested separately. Localhost works because localhost is the only URL anyone registered.

If you use Supabase, Clerk, Auth0 or NextAuth, there is a third place to configure as well: the auth service has its own allowed redirect list and its own Site URL.

The usual causes, most common first

  1. The redirect URI does not match exactly

    How to tell The provider shows an error such as "redirect_uri_mismatch". The registered value and the one your app sends differ in scheme, host, path or a trailing slash, or the live domain was never registered at all. Preview deployments are different hosts, so each also needs registering or a wildcard where the provider allows one.

    The fix Register the exact production callback URL in the provider console, character for character, and remove the habit of editing it on the fly.

  2. The Site URL or allowed redirects still point at localhost

    How to tell Login succeeds, then the browser lands on http://localhost:3000. In Supabase, the Site URL defaults to localhost, and redirects to anything outside the allow list fall back to the Site URL.

    The fix Set the Site URL to the production domain and add every legitimate callback and preview pattern to the redirect allow list in the auth service settings.

  3. The provider app is not in production mode

    How to tell Only you (or a few named people) can sign in. Others see "Access blocked" or a warning that the app has not completed verification. Google consent screens in testing status allow only listed test users.

    The fix Move the consent screen out of testing and complete whatever verification the requested scopes require, or ask only for basic profile and email scopes that do not need review.

  4. The client id or secret is wrong for this environment

    How to tell The callback logs "invalid_client" or "invalid_grant". The secret was pasted with a trailing newline, belongs to a different provider app (development versus production), or exists only in the Preview scope.

    The fix Set the production client id and secret in the production environment, check them for stray whitespace, and rotate the secret if it was ever pasted into a chat or committed to a repository.

  5. The state or PKCE cookie did not survive the round trip

    How to tell Errors mention "invalid state" or a missing code verifier. Login starts on one host (the apex domain) and the callback arrives on another (www), so the cookie set at the start is not sent back. A strict cookie setting can cause the same.

    The fix Serve the whole login flow from one canonical host, redirect the other to it, and keep cookie attributes consistent between the route that starts login and the route that finishes it.

  6. The callback route never completes the exchange

    How to tell The user comes back to /login?code=... with the code still in the URL, or the callback route returns 404 or 500. The route is missing, was renamed, is protected by auth middleware, or throws before creating the session.

    The fix Make sure the callback route exists at the registered path, exclude it from auth middleware, and read the function log for the exchange call. Create the session before redirecting anywhere.

How we diagnose it

  1. Capture the failing URL. Reproduce the login in a private window with the network tab open, and copy the final URL including its query string. An error and error_description parameter usually names the problem outright.
  2. Compare the three redirect settings. Line up the URI your code sends, the one registered with the provider and the allow list in the auth service. They must agree, and the production value must be present in all three.
  3. Check the provider app status and scopes. Confirm whether the provider app is in testing or production, which users are allowed and whether any requested scope triggers review.
  4. Follow the callback in the logs. Read the production function log for the callback request: did it arrive, was the code present, did the exchange succeed, was the session cookie written for the right domain?
  5. Test from a clean browser as a stranger. A fix is confirmed only by a first-time sign-in from an account that is not yours, on the live domain, in a window with no existing cookies.

What fixing it changes

  • The production callback URL is registered with the provider and in the auth service, and the Site URL is the live domain.
  • The provider app is out of testing mode, so real users can sign in.
  • One canonical host serves the whole login flow, with cookies that survive the round trip.
  • The callback route is outside the auth middleware and fails with a readable message.
  • Secrets are per-environment and stored only in the host's secret settings.

When you do not need us

If the provider error says redirect_uri_mismatch, you almost certainly need five minutes in a settings page, not an engineer. The same goes for a Site URL left on localhost. Pay for help when the redirects look right and sign-in still fails, or when auth, middleware and cookies are tangled together.

Your next step

Auth is the part of an AI-built app most likely to look right and be wrong for strangers. A Ship Check includes a security and data read of the login flow, not just whether it loads.

Ship Check, $299: A senior engineer reads your codebase and sends a written verdict (fine, fixable or rewrite), what will break first, a security and data read, and a costed plan, within 48 hours of repo access or it is free. It has a 7-day money-back and is credited against a Ship Fix. Or: Book a call.

Get a Ship Check, $299 →See all services and prices →
FAQ

Straight answers

Why does Google sign-in work for me but not for my users?
Most often the Google consent screen is still in testing status, which allows only the test users you listed. Publishing the app, and completing verification if your scopes need it, opens sign-in to everyone.
Why does login send me back to localhost?
The auth service's Site URL (or the redirect you passed) is still set to localhost. Redirect targets not on the allow list fall back to the Site URL, so a stale value takes everyone there. Set it to your live domain.
Do preview deployments need their own redirect URI?
Yes, because each preview deployment is a different host. Register a wildcard pattern where the provider or auth service supports one, or test sign-in only on the production domain.
Keep reading
Why are my signup and confirmation emails not arriving?By symptom. Confirmation, magic link and receipt emails that never arrive: test-sender limits, unauthenticated domains, spam filters, link scanners, and how to check each.How do I know if Supabase RLS is off, and what do I do?By symptom. Row level security off means anyone with your public key can read or change that table. How to check in a minute, the six ways RLS fails, and the fix.Why does my app work locally but break in production?By symptom. Production differs from your laptop in environment variables, database, file system, Node version and network rules. The usual causes and how to check each.What should I check before launching my AI-built app?By stage. A launch-week checklist for apps built with AI tools: who can see whose data, secrets, the real payment path, email, backups, monitoring and a rollback plan.What breaks in an AI-built B2B SaaS app before launch?By vertical. For B2B SaaS built with AI tools: tenant isolation, roles, invites, billing and the enterprise asks that arrive first. What breaks and how to check.Why is my Stripe webhook not firing?By symptom. Stripe is usually sending the event and your server is refusing it. How to read the delivery log, the six usual causes, and the fix for each.
Sources
  1. 1. Supabase: Redirect URLs, Supabase: Site URL, the redirect allow list and wildcard patterns for preview deployments.
  2. 2. Google: Using OAuth 2.0 for web server applications, Google: Redirect URI rules and the authorization code flow.
  3. 3. RFC 6749: The OAuth 2.0 Authorization Framework, IETF: The standard error codes returned on the redirect, including invalid_client and invalid_grant.
Why is my OAuth callback not working? | Continuum