Notes
Error 400: redirect_uri_mismatch, explained character by character
Andrej Lauko, ARLing · 2026-09-06
You click "Sign in with Google" and land on a Google page that says Error 400: redirect_uri_mismatch, sometimes with a "Request details" box showing the redirect_uri your app sent. Nothing else runs: Google refuses the request before your callback route ever sees it. Whatever the cause, the fix has the same shape: the redirect_uri your code sends has to match, byte for byte, an entry in Authorized redirect URIs on your OAuth 2.0 Client. This is a Google OAuth setting, not a NextAuth, Supabase, or Firebase bug specifically, but each of those stacks computes the redirect_uri differently, so the exact fix depends on which one you use. Below are the causes in the order they tend to show up, and how to fix each one.
01
The redirect_uri isn't in Authorized redirect URIs at all
Google checks the incoming redirect_uri against an exact list: Authorized redirect URIs on your OAuth 2.0 Client, under Google Cloud Console → APIs & Services → Credentials → your OAuth 2.0 Client ID. If the value your app sent isn't in that list character for character, this is the error you get, before your callback route runs at all.
Fix: copy the redirect_uri from the error page, or from the address bar, or from your browser's Network tab on the request to `accounts.google.com/o/oauth2/v2/auth`, and add it to Authorized redirect URIs on the client your app actually uses. If you have more than one Google Cloud project, confirm you are editing the right one.
02
http instead of https
Google requires the https scheme for every redirect_uri except localhost. Localhost URIs, including 127.0.0.1 and [::1], are exempt from this rule; every other host must use https.
Before: http://example.com/api/auth/callback/google
After: https://example.com/api/auth/callback/google
If your redirect_uri uses http on a real domain, that alone is enough to trigger the error, independent of anything else being correct.
03
www vs apex, or localhost vs 127.0.0.1
Google treats `https://example.com` and `https://www.example.com` as two unrelated hosts, not variants of one site. It treats `localhost` and `127.0.0.1` the same way: same loopback purpose, different strings, different match.
Fix: use exactly one form in your code and register that same form in Authorized redirect URIs. If your app redirects www to the apex domain (or the reverse) before the OAuth request fires, the redirect_uri has to reflect the host you land on after that redirect, not before it.
04
A trailing slash or letter case in the path
Google's own validation rule is explicit: the scheme, case, and trailing slash must all match. `/api/auth/callback/google` and `/api/auth/callback/google/` are two different redirect_uris to Google, and so are `/Callback` and `/callback`.
Fix: match the path exactly, including case and the presence or absence of a trailing slash. django-allauth's callback path is fixed with a trailing slash, `/accounts/google/login/callback/`; most other stacks have none.
05
A query string or a fragment on the redirect_uri
A redirect_uri can't carry a query string or a fragment. Google's validation rejects both outright: no `?state=...` and no `#section`, even if your app would otherwise strip it before use.
Fix: send the bare callback URL only. Any state or code your app needs after the redirect belongs in OAuth's own `state` parameter, never appended to redirect_uri.
06
NextAuth, Supabase, Firebase, and Django each expect a different callback
Each library computes its own redirect_uri, and the value Google needs depends on which one you use:
- NextAuth v4 / Auth.js v5: <origin>/api/auth/callback/google by default (Qwik and SvelteKit use /auth/callback/google instead; if you set a custom basePath, replace /api/auth with it).
- Supabase: https://<project-ref>.supabase.co/auth/v1/callback, or your custom auth domain if you've set one. This is Supabase's own callback, not your app's URL: Google redirects to Supabase first, and Supabase redirects to your app second.
- Firebase: https://<authDomain>/__/auth/handler; the trailing /__/auth/handler is required, and authDomain defaults to <project-id>.firebaseapp.com.
- django-allauth: <origin>/accounts/google/login/callback/, a fixed path regardless of your own URLconf.
A frequent mistake with Supabase and Firebase specifically: registering your app's own URL instead of the provider's. Fix it by pointing Authorized redirect URIs at the provider's callback, not your app.
07
Native and command-line apps need a different client type
Native and CLI flows don't use a Web application OAuth client, and their redirect_uri looks different. google-auth-oauthlib's `InstalledAppFlow.run_local_server()` uses `http://localhost:<port>/` on a Desktop client, which accepts any loopback port without pre-registering it. `gcloud auth application-default login` with a custom `--client-id-file` runs its listener on the fixed port 8085, `http://localhost:8085/`. Expo's `makeRedirectUri()` returns a custom scheme in a development build, or an `exp://` URL in Expo Go, which Google can't register at all; test with a development build instead.
A custom scheme, anything other than http or https, only works with an Android, iOS, or Desktop client type. Pasting it into a Web application client's redirect URIs produces this same error.
08
An invisible character you can't see
A trailing space or a zero-width character, picked up from a copy-paste, looks identical to the correct redirect_uri but fails Google's exact match. Google's validation explicitly rejects non-printable characters in a redirect_uri.
Fix: retype the value instead of pasting it, or paste it into a plain-text editor first, where a stray space or zero-width character becomes visible.
Checklist
- Compare byte-for-byte: scheme (http vs https), host (www vs apex, localhost vs 127.0.0.1), port, and path, including trailing slash and letter case.
- Confirm there is no query string or fragment on the redirect_uri.
- Confirm you are editing the OAuth 2.0 Client ID your app's client ID and secret actually reference, and the right Google Cloud project if you have more than one.
- Add the exact value to Authorized redirect URIs on that client.
- Re-check after every deploy: Google Cloud Console and your environment variables drift independently of each other.
- Just saved a change? Google says propagation can take a few minutes up to a few hours.
Check it in 30 seconds
Paste the redirect_uri from the error, your Authorized redirect URIs, and your stack into the [Google OAuth redirect_uri_mismatch Doctor](https://arling.sk/google-oauth-redirect-doctor/) to see the exact difference and the fix.
Sources
- Google Identity, "OAuth 2.0 for Web Server Applications: redirect URI validation rules": developers.google.com/identity/protocols/oauth2/web-server#uri-validation
- Google Identity, "OAuth 2.0 for Desktop and Mobile Apps": developers.google.com/identity/protocols/oauth2/native-app
- Auth.js, "Google provider": authjs.dev/getting-started/providers/google
- Supabase, "Login with Google": supabase.com/docs/guides/auth/social-login/auth-google
- Firebase, "Redirect best practices for authentication": firebase.google.com/docs/auth/web/redirect-best-practices
- django-allauth, "Google provider": docs.allauth.org/en/latest/socialaccount/providers/google.html
Originally published at arling.sk. Written by Andrej Lauko with AI assistance; every rule links to the official documentation it comes from.
Top comments (0)