Skip to content
AscendQ.ca — Websites, Apps & SEO Systems

Google & OAuth 8 min read

How to Fix "Error 400: redirect_uri_mismatch"

Access blocked: This app's request is invalid
You can't sign in because this app sent an invalid request. You can try again later, or contact the developer about this issue.
Error 400: redirect_uri_mismatch

This is one of the most-searched OAuth errors on the internet, and the fix is almost always mechanical once you know what Google is actually comparing. Google requires the redirect URI your app sends in the sign-in request to EXACTLY match one of the "Authorized redirect URIs" registered on your OAuth client in Google Cloud Console. Not "same domain". Not "close enough". Byte-for-byte identical — scheme, host, port, path, and trailing slash all count.

The frustrating part is that the mismatch is often invisible: your code builds the redirect URI dynamically, and something in your hosting stack (a reverse proxy, a CDN, a load balancer) quietly changes what your app thinks its own hostname is. We hit exactly that case on one of our own production systems this week — the full story is below, because it is the variant almost no tutorial covers.

You're In The Right Place If…

  • Clicking "Sign in with Google" (or connecting any Google API) shows "Access blocked: This app's request is invalid"
  • The error details show "Error 400: redirect_uri_mismatch"
  • It may work on localhost or a staging URL but fail in production — or the other way around
  • It worked last week and broke after a deployment, domain change, or proxy/CDN change

What Google Is Actually Checking

When your app starts an OAuth flow, it sends users to accounts.google.com with a redirect_uri parameter — the address Google should send the user back to with the authorization code. Google looks up your OAuth client ID, pulls its list of Authorized redirect URIs, and compares the parameter against that list with strict string equality. Any difference — http vs https, www vs no-www, a port number, a missing trailing slash, a different subdomain — fails the check and shows users the "Access blocked" screen.

This is a security feature, not pedantry: if Google matched loosely, an attacker could register a lookalike path and capture your users' authorization codes. So Google will never "fix" this by being more lenient — your two URIs simply have to match.

First: Find Out What Your App Actually Sent

Do not guess. On the error screen, click "error details" (or add a developer email and check the details link) — Google shows you the exact redirect_uri it received. Alternatively, before clicking sign-in, copy the full accounts.google.com URL from the browser address bar (or your server logs) and read the redirect_uri query parameter out of it. That string is your ground truth.

Now open Google Cloud Console → APIs & Services → Credentials → your OAuth 2.0 Client ID, and compare it character-by-character against every entry under "Authorized redirect URIs". In our experience the mismatch is visible within seconds once you have both strings side by side.

The Five Classic Mismatches

  • Scheme: your app sent http:// but you registered https:// (common on localhost and behind TLS-terminating proxies)
  • Host: www.yourdomain.com vs yourdomain.com — these are different URIs to Google
  • Port: http://localhost:3000/callback registered, but the app runs on :5173 (or vice versa)
  • Path: /api/auth/callback vs /api/auth/callback/ — the trailing slash matters
  • Environment drift: you registered the staging URL but are testing on production (or a preview deployment URL that changes on every deploy)

The Hidden Cause: Your Reverse Proxy Is Lying To Your App

Here is the variant that burns experienced developers. Many frameworks build the redirect URI dynamically from the incoming request: they read the Host (or X-Forwarded-Host) header and construct https://{host}/auth/callback. That works — until your app sits behind a reverse proxy, CDN worker, or load balancer that forwards requests to an internal hostname.

When that happens, your app sees the INTERNAL host (something like app-internal.your-platform.com) instead of your public domain, builds the redirect URI from it, and sends Google a URI you never registered. From the outside everything looks configured correctly, and the error makes no sense until you inspect the actual redirect_uri parameter being sent.

The robust fix: stop deriving the redirect URI from request headers. Pin it in configuration — one environment variable that holds the exact public callback URL — and use that value both when building the sign-in URL and when exchanging the authorization code for tokens (the token exchange must use the same redirect_uri too).

# .env — pin the public callback explicitly
GOOGLE_OAUTH_REDIRECT=https://yourdomain.com/api/google/oauth/callback

# app code — prefer the pinned value over anything from request headers
redirect_uri = os.environ.get("GOOGLE_OAUTH_REDIRECT") or derive_from_request(request)

After You Fix It: Propagation And Caching

Changes to Authorized redirect URIs in Google Cloud Console can take a few minutes to a few hours to propagate (Google's own docs say "a few minutes to several hours" — in practice 5–15 minutes is typical). If you are sure the strings match and it still fails, wait ten minutes and try again in a private window before changing anything else.

The Fix, Step By Step

  1. 1

    Capture the exact redirect_uri your app sends

    Click "error details" on Google's block screen, or copy the accounts.google.com URL from the address bar and read its redirect_uri query parameter. This string is your ground truth — never work from what you think the app sends.

  2. 2

    Compare against your registered URIs character-by-character

    Google Cloud Console → APIs & Services → Credentials → your OAuth 2.0 Client ID → Authorized redirect URIs. Check scheme (http/https), host (www vs bare), port, path, and trailing slash. Any single-character difference is the bug.

  3. 3

    Register the exact URI (or fix what the app sends)

    Either add the URI your app actually sends to the authorized list, or — better — make the app send the URI you intend. Prefer one canonical public URL.

  4. 4

    If you are behind a proxy or CDN: pin the redirect URI in config

    Do not build the redirect URI from Host/X-Forwarded-Host headers. Set it in an environment variable (e.g. GOOGLE_OAUTH_REDIRECT=https://yourdomain.com/api/oauth/callback) and use that value in both the sign-in URL and the token exchange.

  5. 5

    Wait for propagation, then retest in a private window

    Console changes take ~5–15 minutes (occasionally longer). Test in an incognito window so cached OAuth state doesn't mislead you.

From the trenches

How we hit this on a real production site

We hit this exact error connecting Google Search Console to our own studio dashboard. The OAuth client was configured correctly, the callback URL was registered — and Google still blocked with redirect_uri_mismatch.

The cause was the hidden proxy variant: our public domain routes /api traffic through a Cloudflare Worker to the backend host. The backend built its redirect URI from the forwarded Host header, so instead of sending https://ourdomain.ca/api/google/oauth/callback it was sending the internal backend hostname — a URI Google had never seen.

The fix was four lines: read a pinned GOOGLE_OAUTH_REDIRECT environment variable first and only fall back to header-derived URIs when it is unset. Deployed, reconnected, working — total time from error to fix, about twenty minutes. If you would rather not spend your afternoon on this, that is literally what we do for clients.

Frequently Asked Questions

Why does it work on localhost but not in production?

You almost certainly registered your localhost callback (http://localhost:3000/...) but not the production one — or production sits behind a proxy that changes the hostname your app sees. Register the exact production URI and pin it in configuration rather than deriving it from request headers.

I fixed the URI in Google Cloud Console but still get the error. Why?

Two usual reasons: propagation delay (wait 5–15 minutes and retry in a private window), or the app sends a subtly different URI than you think — recheck the actual redirect_uri parameter in the accounts.google.com URL, not your assumption of it.

Does the redirect URI matter again after the user approves access?

Yes. When your server exchanges the authorization code for tokens, it must send the same redirect_uri used in the sign-in request. If the two differ, the token exchange fails with invalid_grant even though the consent screen worked.

Can I use a wildcard so any subdomain works?

No. Google does not allow wildcards, fragments, or path patterns in authorized redirect URIs. Register each exact URI you use — one per environment is the clean pattern.

Still stuck? Send us the exact error.

Paste the exact error message you're seeing and where it happens. We'll take a look — if it's quick we'll point you at the fix, and if it's deeper we'll tell you honestly what it takes.

Reach the Summit

Rather have someone just fix it?

This is literally what we do all day — websites, SEO, performance, and the weird errors in between.

Start the Conversation