Skip to content

oauth-callback-url-mismatches

Better Auth3 min readships at docs/solutions/better-auth/oauth-callback-url-mismatches.md

Every OAuth provider compares the redirect_uri your app sends with the list registered for your client, character by character. When they differ the provider shows its own error page and your code never runs, so there is nothing in your logs. This is the most common reason "Continue with Google" works locally and fails in production.

What Better Auth sends

Better Auth builds the redirect URI from baseURL (the BETTER_AUTH_URL environment variable):

<BETTER_AUTH_URL>/api/auth/callback/<provider id>

http://localhost:3000/api/auth/callback/google locally, https://example.com/api/auth/callback/google in production. Open the provider's error page and read the redirect_uri in its URL: that is exactly what was sent. Compare it with the registered list.

The one-character differences

SentRegisteredWhy
http://…https://…BETTER_AUTH_URL set to http in production, or a proxy terminating TLS
https://www.example.com/…https://example.com/…the site answers on both hosts, the env names one
…/callback/google/…/callback/googletrailing slash in BETTER_AUTH_URL (strip it)
http://127.0.0.1:3000/…http://localhost:3000/…different hosts to the provider, and to your cookies
https://my-app-git-branch.vercel.app/…production URL onlypreview deployments have their own origin

Provider by provider

Google (Error 400: redirect_uri_mismatch). Google Cloud console, Google Auth Platform, Clients, your Web client, "Authorized redirect URIs". Add one line per origin. Changes can take a few minutes to apply. While the app's publishing status is "Testing", only listed test users can sign in, which fails with a different error ("access blocked") that is easy to confuse with this one.

GitHub ("The redirect_uri is not associated with this application"). An OAuth app has exactly one "Authorization callback URL". GitHub accepts a redirect URI that extends it with more path segments, but not a different host or port. Create one OAuth app per environment (local, production) with its own client id and secret.

Microsoft Entra (AADSTS50011: The redirect URI ... does not match). App registrations, your app, Authentication. The URI must be under the Web platform, not "Single-page application" (which fails later with a CORS or token error). Also check "Supported account types" against the tenant you send: common needs "any organizational directory and personal Microsoft accounts", or personal accounts fail with AADSTS50020.

Preview deployments

Preview URLs change per branch, and most providers do not accept wildcards. Pick one:

  • No OAuth on previews. Leave the provider keys unset in the preview environment. With buttons switched on by the keys, previews show email sign-in only. Simplest, and usually enough.
  • A stable preview domain. Give previews a fixed alias (preview.example.com), register its callback, and set BETTER_AUTH_URL to it there.
  • An OAuth proxy. Better Auth's oAuthProxy plugin routes previews through the production callback. More moving parts; reach for it only if testing OAuth on every branch matters.

Also check

  • BETTER_AUTH_URL matches the browser's origin exactly. Better Auth also uses it for the trusted-origin check, so a mismatch breaks sign-in even before OAuth.
  • Behind a reverse proxy, the public origin is what counts, not the internal one the app sees.
  • After changing BETTER_AUTH_URL or the keys, restart the server: the auth config is read once at startup.

Checking your work

  • Click each provider button on each environment and read the redirect_uri on the provider's page (or in the network tab) before consenting.
  • Complete one sign-in per provider per environment after every domain change.