redirect_uri_mismatch is the single most common OAuth2 integration error, and
it's frustrating because the two strings usually look identical.
They aren't. The spec requires exact string comparison — no normalization, no canonicalization, no clever equivalence. That's deliberate: the redirect URI is a security boundary, and any fuzziness in matching is a place to smuggle a redirect. But it means differences that are semantically meaningless still fail.
Here's the full list, roughly by frequency.
1. Trailing slash
registered: https://app.example.com/callback
sent: https://app.example.com/callback/
These are different strings, so they don't match. Semantically most servers treat them as the same resource, which is exactly why this one is so easy to miss.
Frameworks often add or strip the trailing slash for you. Next.js, Django, and Rails all have opinions here, and the opinion may differ between dev and production.
2. http vs https
registered: https://localhost:3000/callback
sent: http://localhost:3000/callback
Common when local development runs over plain HTTP but the registered value was
copied from production. Register both explicitly — most providers allow http
for localhost specifically, as an exception to the HTTPS requirement.
3. Port present, absent, or different
registered: http://localhost/callback
sent: http://localhost:3000/callback
http://localhost and http://localhost:80 are also different strings, even
though 80 is the default. Dev servers that pick a free port when the preferred
one is taken produce a fun intermittent version of this — you registered 3000,
today the app is on 3001.
4. localhost vs 127.0.0.1
registered: http://localhost:3000/callback
sent: http://127.0.0.1:3000/callback
They resolve to the same interface. They are not the same string. Pick one and be consistent; if your framework rewrites the host, register what it actually sends.
5. Case sensitivity in the path
Scheme and host are case-insensitive per RFC 3986. The path is not.
registered: https://app.example.com/Callback
sent: https://app.example.com/callback
Providers vary in how strictly they apply this, which makes it worse — it may work against one and fail against another.
6. Query parameters
registered: https://app.example.com/callback
sent: https://app.example.com/callback?tenant=acme
Many providers reject any redirect URI carrying a query string, and those that allow it require the registered value to include it exactly. Dynamic per-request parameters won't work.
If you need to carry state through the flow, that's what the state parameter is
for. It survives the round trip and you should be validating it anyway.
7. Encoding differences
The redirect URI travels inside the authorization request, so it must be percent-encoded there:
https://auth.example.com/authorize
?redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
Double-encoding (%253A instead of %3A) happens when a value gets encoded
twice by different layers. The server decodes once, gets a mangled string, and
fails the match. This one is invisible in logs unless you're looking closely.
8. Fragments
sent: https://app.example.com/callback#/dashboard
Fragments are explicitly forbidden in redirect URIs. They're also never sent to the server by the browser, so this fails in a confusing way. Hash-router SPAs run into this — route to the callback path, then let the router take over after the exchange.
9. Wildcards you assumed were supported
registered: https://*.example.com/callback
Most providers don't support wildcards at all, and those that do restrict them heavily — typically a single subdomain level, never in the path. The reason is sound: a wildcard is a delegation of trust to anything matching it, and one compromised subdomain becomes an account takeover on the whole domain.
Register each environment explicitly.
10. The environment you forgot
Preview deployments generate a fresh URL per branch — app-git-feature-x.vercel.app
and so on. None of them are registered, so auth breaks on every preview.
Options: a stable alias domain for previews that you register once, or a dedicated application registration for the preview environment.
Working out which one it is
Log the exact redirect_uri your app sends, and diff it character by character
against the registered value. Eyeballing them is unreliable — every cause above
is invisible at a glance.
Two tools that help: the Redirect URI Tester checks a callback URL against a registered value and reports precisely where they diverge, and the URL Parser decomposes both into scheme, host, port, path, and query so the difference is unambiguous. For double-encoding, the Base64 / URL Encoder will decode the parameter as many times as needed.
To configure callbacks on AuthAction, see registering an application.