01
Three reasons Google keeps rejecting a URI that looks right.
Google's redirect_uri check is an exact string comparison. No wildcards, no normalization. These three causes account for almost every mismatch.
-
Exact match only.
Scheme, host, port, path, case, and trailing slash must all match. localhost and 127.0.0.1 count as different hosts. One differing character is enough to fail.
-
Wrong target.
Supabase and Firebase need their own callback URL in Google Console, not your app's URL: Google redirects to them first. NextAuth and Auth.js need a specific /api/auth/callback/google path.
-
Wrong client type.
A Web application OAuth client only accepts http and https URIs. A custom scheme or a loopback port needs a Desktop, Android, or iOS client instead.
02
Fill in what Google and your console show. Get the exact diff.
Nothing you type is sent anywhere. The check runs in your browser, using Google's documented redirect_uri rules.
03
One function. Config in, diagnosis out.
No server, no API key, no signup. doctor-google.js is plain JavaScript: read it, fork it, or run it in your own scripts or CI.
import { diagnose } from './doctor-google.js'; // or, loaded globally: const { diagnose } = window.GoogleRedirectDoctor; const result = diagnose({ error: { redirectUriFromError, clientType }, console: { authorizedRedirectUris, authorizedJsOrigins, publishingStatus, savedMinutesAgo }, app: { stack, origin, callbackPath, supabaseRef, firebaseAuthDomain, expoUsername, expoSlug, useProxy, port, env }, }); // result { "status": "fail" | "warn" | "pass", "summary": "…", "expected": { stack, redirectUri, redirectUriNote, clientTypeExpected, … }, "problems": [ { severity, code, message, path, value, fix } ], "fixes": [ { title, value, where } ], "checklist": [ "…" ], "disclaimer": "…" }
Everything runs client-side. The form above calls this exact function in your browser. There is no backend, no API key, and no request that carries your config anywhere.
It computes the redirect_uri your stack should send, diffs it byte for byte against what Google saw and against every entry in your Authorized redirect URIs, and reports which part differs: scheme, host, port, path, case, trailing slash, query, or fragment.
04
Free.
No account, no payment, no usage limit: it runs as a static page in your browser, so there is no server to bill for.
Tell me when a new tool lands. New tools only. No newsletter, no sharing. Reply to any mail to be removed.
Thanks. You will hear from us only when something new is live.
Could not save. Write to andrej@arling.sk.
05
Questions developers actually search for.
Straight answers to the same redirect_uri_mismatch questions this tool diagnoses, for when you just need the answer, not the checker.
What does Error 400: redirect_uri_mismatch mean?
Google's OAuth server got a redirect_uri that is not byte-for-byte identical to one of the Authorized redirect URIs on your OAuth 2.0 Client ID. Google's docs are explicit: scheme, host, port, case, and trailing slash must all match. There is no wildcard matching, unlike Supabase's redirect allow-list. Either your app sends the wrong URI, or the right one is not registered yet.
Which redirect URI does NextAuth or Auth.js send to Google?
By default, your site's origin plus /api/auth/callback/google, for example https://myapp.com/api/auth/callback/google. This applies to NextAuth v4 and Auth.js v5, App Router and Pages Router alike. A custom basePath replaces /api/auth with your own prefix. Register that exact string, with no trailing slash, in Google Cloud Console.
What redirect URI do I put in Google Cloud Console for Supabase?
Supabase's own callback: https://<project-ref>.supabase.co/auth/v1/callback, or your custom Supabase auth domain if you set one. Never your app's URL. Google redirects to Supabase first, and only then does Supabase redirect to your app using its own separate Redirect URLs allow-list. Pasting your app's URL here instead is one of the most common causes of this error.
Why does localhost work but 127.0.0.1 does not, or the other way round?
Google treats localhost and 127.0.0.1 as two different hosts, even though both point at your machine. Its exact-match rule does not normalize them. If your code sends one and you registered the other, you will see this error. Register the exact host string your code actually sends.
Why do I still get redirect_uri_mismatch after adding the URI?
Three usual reasons. The value you added is not byte-identical to what your app sends (a stray trailing slash, http vs https, www vs apex, or a hidden character from pasting). You edited the wrong OAuth Client ID or the wrong Google Cloud project. Or the change has not propagated yet: Google's own guidance says it can take minutes up to a few hours.
Can I use a wildcard for Vercel preview deployments?
No. Unlike Supabase's redirect allow-list, Google's Authorized redirect URIs field accepts no wildcards. Every entry is an exact, literal string, and each Vercel preview gets its own *.vercel.app URL. Route the callback through a stable domain, even from previews, or register each preview URL individually.