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
| Sent | Registered | Why |
|---|---|---|
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/google | trailing 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 only | preview 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 setBETTER_AUTH_URLto it there. - An OAuth proxy. Better Auth's
oAuthProxyplugin routes previews through the production callback. More moving parts; reach for it only if testing OAuth on every branch matters.
Also check
BETTER_AUTH_URLmatches 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_URLor 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_urion the provider's page (or in the network tab) before consenting. - Complete one sign-in per provider per environment after every domain change.