The redirect_uri mismatch is the second most common OAuth error after invalid_grant. Every OAuth provider requires that the redirect URI in your request exactly matches a pre-registered value — and “exactly” means character-for-character, including trailing slashes, ports, and protocol. This guide covers every cause and provider-specific fix.

Quick Diagnostic: Which Provider Error Are You Seeing?

Error MessageProviderJump To
Invalid parameter: redirect_uriKeycloakKeycloak
Callback URL mismatchAuth0Auth0
redirect_uri must be a Login redirect URI in the client app settingsOktaOkta
AADSTS50011Azure AD / Entra IDAzure AD
Error 400: redirect_uri_mismatchGoogleGoogle
The redirection URI provided does not match a pre-registered valueForgeRock AMForgeRock
redirect_mismatchAWS CognitoAWS Cognito

Every Cause of redirect_uri Mismatch

Before checking provider-specific fixes, work through this checklist. Most mismatches fall into one of these 10 categories:

1. Trailing Slash

RReegqiusetsetreedd::hhttttppss:://mmyyaapppp..ccoomm//ccaallllbbaacckk/FAILS

A trailing slash makes these two entirely different strings. Every provider uses exact string comparison.

2. Protocol Mismatch (http vs https)

RReegqiusetsetreedd::hhttttpps::mmyyaapppp..ccoommccaallllbbaacckkFAILS

This is the #1 production issue. Your reverse proxy terminates SSL, so the app sees HTTP and constructs http:// in the redirect_uri. See Reverse Proxy Fixes below.

3. Port Mismatch

RReegqiusetsetreedd::hhttttpp:://llooccaallhhoosstt::38000800//ccaallllbbaacckkFAILS

Ports are part of the origin. localhost:3000 and localhost:8080 are different.

Exception: Azure AD ignores port numbers for localhost redirect URIs (RFC 8252 support for native apps).

4. www vs non-www

RReegqiusetsetreedd::hhttttppss:://wmwywa.pmpy.acpopm./ccoaml/lcbaalclkbackFAILS

These are different hostnames. Register both or ensure your app uses a consistent hostname.

5. Case Sensitivity

RReegqiusetsetreedd::hhttttppss:://mmyyaapppp..ccoomm//cCaallllbbaacckkFAILSonmostproviders

All major providers (Keycloak, Auth0, Okta, Azure AD, Google, ForgeRock, Cognito) perform case-sensitive path comparison.

6. redirect_uri Missing from Token Request

RFC 6749 Section 4.1.3: If redirect_uri was included in the authorization request, it MUST also be included in the token request with an identical value. Omitting it from the token exchange causes invalid_grant.

// WRONG — redirect_uri omitted from token request
const tokenResponse = await fetch(tokenEndpoint, {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    client_id: CLIENT_ID
    // Missing redirect_uri!
  })
});

// CORRECT — redirect_uri included and identical to authorization request
const tokenResponse = await fetch(tokenEndpoint, {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    client_id: CLIENT_ID,
    redirect_uri: 'https://myapp.com/callback'  // Exact same value
  })
});

7. URL Encoding Differences

RReegqiusetsetreedd::hhttttppss:://mmyyaapppp..ccoomm/%c2aFlclablalcbkackFAILS(double-encoded)

Some HTTP libraries double-encode the redirect_uri when it’s already URL-encoded as a query parameter. Check the actual request in DevTools.

8. Query Parameters in redirect_uri

RReegqiusetsetreedd::hhttttppss:://mmyyaapppp..ccoomm//ccaallllbbaacckk?state=abcFAILSonGoogle

Google does not allow query parameters in registered redirect URIs. Other providers have varying behavior — check per-provider documentation.

9. Fragment (#) in URL

Registered:https://myapp.com/#/callbackREJECTEDperRFC6749

RFC 6749 prohibits fragment components in redirect URIs. This breaks React apps using HashRouter — switch to BrowserRouter.

10. Multiple Registered URIs, Wrong One Used

If you’ve registered https://myapp.com/callback-dev and https://myapp.com/callback-prod but your app sends https://myapp.com/callback, it fails. The requested URI must match one of the registered URIs exactly.

Provider-Specific Error Messages and Configuration

Keycloak

Error: Invalid parameter: redirect_uri displayed on an error page. Server log:

WAReNrr[oorr=gi.nkveaylcildo_arke.deivreenctts_]urtiy,per=eLdOiGrIeNc_tE_RuRrOiR=,htctlpise:n/t/Imdy=ampyp-.acpopm,/callback/

Configuration: Admin Console → Clients → [Client] → Settings → Valid Redirect URIs

Wildcard support: Trailing * only. https://myapp.com/* matches https://myapp.com/callback and https://myapp.com/any/path.

Common Keycloak mistakes:

  • Leaving Valid Redirect URIs empty (all redirect URIs rejected)
  • Adding * alone (too permissive, blocked in newer versions)
  • Forgetting to add the logout redirect URI to Valid Post Logout Redirect URIs

Auth0

Error: Callback URL mismatch. The provided redirect_uri is not in the list of allowed callback URLs.

Configuration: Dashboard → Applications → [App] → Settings → Allowed Callback URLs

Wildcard support: Subdomain wildcard: https://*.myapp.com. Does NOT match multi-level subdomains (https://a.b.myapp.com fails).

Auth0-specific issue: Auth0 has separate fields for Allowed Callback URLs (for login) and Allowed Logout URLs (for logout). If your logout redirect fails, check the logout URLs field, not the callback URLs.

Okta

Error: 400 Bad Request — The 'redirect_uri' parameter must be a Login redirect URI in the client app settings.

Configuration: Admin Console → Applications → [App] → General → Login redirect URIs

Wildcard support: Early Access feature — enable “Allow wildcard * in login URI redirect” for subdomain wildcards.

Azure AD / Entra ID

Error: AADSTS50011 — The redirect URI 'https://myapp.com/callback' specified in the request does not match the redirect URIs configured for the application.

Configuration: Azure Portal → Microsoft Entra ID → App registrations → [App] → Authentication → Redirect URIs

Azure AD-specific issues:

  1. Platform type matters. URIs registered under “Web” cannot be used for SPA flows. Move your URI to the “Single-page application” platform if you’re calling the token endpoint from JavaScript (see AADSTS9002327).

  2. Trailing slash auto-append. Azure AD appends a trailing slash to URIs without a path segment: https://myapp.com becomes https://myapp.com/. URIs with a path segment are NOT modified.

  3. Max 256 redirect URIs for work/school accounts, 100 for mixed audience apps.

  4. Unsupported characters: ! $ ' ( ) , ; are not allowed in redirect URIs.

Lookup any error: https://login.microsoftonline.com/error?code=AADSTS50011

Google

Error: Error 400: redirect_uri_mismatch — The redirect URI in the request, https://myapp.com/callback, does not match the ones authorized for the OAuth client.

Configuration: Google Cloud Console → APIs & Services → Credentials → OAuth Client → Authorized redirect URIs

Google restrictions:

  • No wildcards
  • No query parameters in registered URIs
  • HTTPS required (except localhost)
  • Case-sensitive

ForgeRock AM

Error: redirect_uri_mismatch — The redirection URI provided does not match a pre-registered value.

Configuration: AM Admin Console → Applications → OAuth 2.0 → Clients → [Client] → Redirection URIs

Wildcard support: Optional “Allow wildcard ports” setting. Trailing * matches any path suffix.

AWS Cognito

Error: Redirects to ?error=redirect_mismatch

Configuration: AWS Console → Cognito → User Pools → [Pool] → App integration → App client → Callback URL(s)

Restrictions: No wildcards, HTTPS required (except http://localhost), exact match only.

Wildcard Support Comparison

ProviderPath WildcardSubdomain WildcardPort WildcardNotes
Keycloak/* trailingNoNoMost permissive
Auth0No*.domainNoSingle-level only
OktaNo*.domain (EA)NoRequires feature flag
Azure ADNo*.domain (limited)localhost onlyWork/school only
GoogleNoNoNoStrictest
ForgeRock/* trailingNoYes (optional)
CognitoNoNoNoStrictest

Reverse Proxy Fixes

The most common production issue: your reverse proxy terminates SSL, so the backend app constructs http:// redirect URIs instead of https://.

Nginx

location / {
    proxy_pass http://backend:8080;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;
    proxy_set_header Host              $host;
}

Apache

RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host  "%{HTTP_HOST}s"

Application Configuration

Your application must trust and use these forwarded headers:

Spring Boot:

server.forward-headers-strategy=framework

Express.js:

app.set('trust proxy', true);

Next.js / Auth.js:

AUTH_TRUST_HOST=true
NEXTAUTH_URL=https://myapp.com

Django:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True

Framework-Specific Issues

React: HashRouter vs BrowserRouter

HashRouter puts # in the URL (http://localhost:3000/#/callback), which violates RFC 6749’s prohibition on fragments in redirect URIs. OAuth providers strip or reject the fragment.

Fix: Use BrowserRouter for OAuth callback routes:

import { BrowserRouter, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Route path="/callback" component={OAuthCallback} />
    </BrowserRouter>
  );
}

Spring Security Behind a Proxy

Spring Security constructs the redirect_uri from the incoming request. Behind an SSL-terminating proxy, it uses http:// instead of https://.

Fix: Add ForwardedHeaderFilter:

@Bean
ForwardedHeaderFilter forwardedHeaderFilter() {
    return new ForwardedHeaderFilter();
}

And set server.forward-headers-strategy=framework in application.properties.

Passport.js / Express

Passport auto-detects the callback URL from the request. Behind a proxy, req.protocol returns http.

Fix: Set trust proxy and explicitly define the callback URL:

app.set('trust proxy', true);

passport.use(new GoogleStrategy({
  clientID: CLIENT_ID,
  clientSecret: CLIENT_SECRET,
  callbackURL: 'https://myapp.com/auth/google/callback'  // Explicit, not auto-detected
}, verifyCallback));

Next.js / NextAuth

NextAuth constructs callback URLs from request headers. In Docker or behind a proxy, the hostname may be the container’s internal hostname.

Fix: Set NEXTAUTH_URL (or AUTH_URL in Auth.js v5):

NEXTAUTH_URL=https://myapp.com
# or for Auth.js v5
AUTH_URL=https://myapp.com
AUTH_TRUST_HOST=true

Debugging with DevTools

Step 1: Open DevTools → Network tab → check “Preserve log”

Step 2: Initiate the OAuth login flow

Step 3: Find the authorization request (302 redirect to the IdP). Copy the redirect_uri parameter value.

Step 4: Find the token exchange request (POST to /token). Compare the redirect_uri in the POST body with Step 3.

Step 5: Compare both values character-by-character against your provider’s registered URIs.

# Reproduce with curl to isolate the issue
curl -v "https://your-idp.com/authorize?\
response_type=code&\
client_id=YOUR_CLIENT_ID&\
redirect_uri=https://myapp.com/callback&\
scope=openid" 2>&1 | grep -i "location\|error"

If the IdP returns an error page instead of redirecting, the redirect_uri doesn’t match any registered value.