ESC

Type to search the knowledge base.

Open Redirects

Unsafe redirect targets in login return URLs and how to allowlist paths without breaking product flows.

intermediate3 min read
  • security
  • open-redirect
  • oauth
  • url
  • phishing

An open redirect lets attackers craft a link on your origin that bounces users to a malicious site. It powers phishing (“looks like bank.example”) and some token theft flows. Frontend and backend both introduce them via ?next= parameters and OAuth redirect_uri mistakes.

Docs: OWASP Unvalidated Redirects, CWE-601.

Exploit sketch

https://app.example/login?next=https://evil.example/phish

After login:

// BAD
location.assign(new URLSearchParams(location.search).get('next'));

User sees your domain in the first hop; lands on a pixel-perfect phishing page.

Also bad: protocol-relative and tricky paths

//evil.example
/\evil.example
https://app.example.evil.example

Naive startsWith('https://app.example') can fail open on subdomain tricks depending on check quality.

Safe patterns

Allowlist relative paths only

function safeInternalPath(raw, fallback = '/app') {
  if (typeof raw !== 'string' || !raw.startsWith('/')) return fallback;
  if (raw.startsWith('//') || raw.includes('://')) return fallback;
  if (raw.includes('\\')) return fallback;
  return raw;
}

const next = safeInternalPath(params.get('next'));
location.assign(next);

Allowlist named routes

const ALLOWED = new Set(['dashboard', 'settings', 'billing']);
const next = ALLOWED.has(params.get('next')) ? `/${params.get('next')}` : '/dashboard';

Server-side enforcement

Never trust the client alone — server should validate redirect targets after auth.

OAuth redirect_uri

Register exact redirect URIs with the IdP. No wildcards unless you fully understand the provider’s matching rules. Authorization codes bound to exact URIs stop many thefts.

UX without open redirects

  • Store return path in server session during login.
  • Or use a signed, short-lived state blob.
  • Flash messages instead of round-tripping untrusted URLs.

Interview out-loud

“Open redirects turn our domain into a phishing trampoline via unchecked next URLs. I only allow relative internal paths or an allowlist, validate on the server, and register exact OAuth redirect URIs.”

Reviewer prompts

  • What is the asset (session, PII, money movement)?
  • What is the attacker capability (web, XSS, network, dependency)?
  • Which control fails closed if misconfigured?
  • Is the server still enforcing authz?
  • Any new third party, iframe, or URL sink?

Residual risk note

Browser controls reduce likelihood and impact; they do not eliminate bugs. Prefer defense in depth: safe defaults in code, strict headers, and monitoring (CSP reports, auth anomaly alerts). When product pressure weakens a control, write down the accepted risk and a revisit date.

Test cases to automate

next=https://evil.example
next=//evil.example
next=/\evil.example
next=/%09/evil.example
next=https://app.example.evil.example
next=/settings  (allow)
next= (fallback)

Assert each login and OAuth completion path against this table in integration tests. Include mobile deep-link handlers if your app bridges to WebViews.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides