What this usually means
Redirect URLs are often built from the current request's host header, a configured base URL, or a hardcoded string. If the base URL is hardcoded, it only works in one environment. If it is derived from the request, a misconfigured proxy or load balancer can pass the wrong host header. OAuth flows are especially vulnerable because the redirect URL must exactly match what is registered with the OAuth provider.
The first ten minutes \u2014 establish facts before touching code.
- 1Find the redirect URL construction code. Is it hardcoded? Is it reading an env var? Is it derived from the request?
- 2Check the `BASE_URL` or `SITE_URL` environment variable in production. Is it set correctly?
- 3Check if the app is behind a proxy or load balancer. The `Host` header might be the internal service name, not the public domain.
- 4Check OAuth provider settings. The registered redirect URL must match exactly the URL the app generates.
- 5Check if the redirect uses `window.location.origin` on the frontend. If the frontend is served from a different domain, this picks up the wrong origin.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchEnvironment variable for base URL — `BASE_URL`, `SITE_URL`, `NEXT_PUBLIC_SITE_URL`
- searchRedirect construction code — server-side redirects in auth handlers, middleware, or controllers
- searchOAuth provider dashboard — registered redirect URIs
- searchProxy/CDN config — X-Forwarded-Host, X-Forwarded-Proto headers
- searchFrontend code — any client-side redirects using `window.location` or `router.push()`
- searchServer framework's trust proxy setting — Express `app.set('trust proxy', true)`, Next.js `trustHost`
Practical causes, not theory. These are the things you will actually find.
- warningHardcoded redirect URL pointing to localhost or a development domain
- warning`BASE_URL` environment variable is not set in production — falls back to a development default
- warningProxy/CDN passes internal hostname as the `Host` header instead of the public domain
- warning`X-Forwarded-Proto` header is not set or not trusted — redirect URL uses `http` instead of `https`
- warningOAuth redirect URI registered with the provider does not match the generated URL exactly (trailing slash, www prefix)
- warningFrontend builds the redirect URL from `window.location` which picks up the wrong origin in an iframe or SSR context
Concrete fix directions. Pick the one that matches your root cause.
- buildSet `BASE_URL` environment variable explicitly in every environment — never rely on request headers alone
- buildConfigure your server framework to trust proxy headers: Express `app.set('trust proxy', 1)`, Next.js `trustHost: true`
- buildUse a redirect builder function that constructs URLs from the configured base URL, not from the request
- buildRegister multiple redirect URIs with OAuth providers — one for each environment plus a trailing-slash variant
- buildAdd a smoke test that follows a redirect chain and asserts the final URL is correct
A fix you cannot prove is a guess. Close the loop.
- verifiedDeploy to staging. Log in and verify the redirect lands on the staging dashboard, not localhost.
- verifiedUse curl to follow the redirect: `curl -v -L https://example.com/login/callback?code=...` and check the Location headers.
- verifiedTest with and without a trailing slash in the OAuth redirect URI.
- verifiedTest HTTP to HTTPS redirect: accessing the HTTP version should redirect to HTTPS without losing the path.
- verifiedDeploy to production and run the same verification.
Things that make this bug worse or harder to find.
- warningHardcoding any URL that includes a domain name
- warningNot setting the BASE_URL in every environment explicitly
- warningNot testing OAuth redirect flows in staging before production deploy
- warningAssuming the request's Host header is always the public domain
- warningForgetting to register the production redirect URI with the OAuth provider