What Open Redirects Are and Why They Keep Appearing

An open redirect exists whenever a web application accepts a user-controlled URL parameter and issues an HTTP redirect to that value without validation. The classic shape is straightforward:

https://example.com/login?return_to=https://evil.example

After a successful login, the application redirects the browser to whatever return_to contains. If there is no validation, the attacker controls the destination. The user sees a legitimate domain in the initial link, trusts it, clicks through, and lands on a phishing page or malware dropper.

OWASP classifies open redirects under Unvalidated Redirects and Forwards. They appear in the OWASP Top 10 periodically and remain a fixture in bug-bounty programmes because developers consistently underestimate the trust chain they break.

The vulnerability is not theoretical. Open redirects are a practical building block in phishing campaigns, OAuth token theft, and session hijacking. They turn your domain into a trusted launchpad for attacks against your own users.

Why OAuth and SSO Flows Are Especially Vulnerable

OAuth 2.0 and OpenID Connect rely heavily on redirect URIs. The authorization server sends the user back to the client application at a registered redirect_uri after authentication. If that redirect URI validation is loose, an attacker can:

  1. Steal authorization codes by registering a redirect URI that points to their server
  2. Harvest tokens from the fragment portion of implicit-flow responses
  3. Chain redirects through an open redirect on the legitimate client to exfiltrate tokens to an external domain

SSO flows compound the risk. A single identity provider serves multiple relying parties. One open redirect on any relying party can be leveraged to phish users across the entire SSO ecosystem, because the initial URL genuinely originates from the identity provider's domain.

The redirect_uri Problem in Practice

The OAuth 2.0 specification (RFC 6749, Section 3.1.2.2) requires exact matching of redirect URIs. In practice, many implementations allow:

  • Subdirectory matching (https://app.example/callback matches https://app.example/callback/anything)
  • Wildcard subdomains (*.example.com)
  • Localhost with any port for development (which then leaks into production)

Each of these creates an attack surface. Exact-match validation is the only safe default.

OWASP Prevention Patterns

Pattern 1: Allowlist of Permitted Destinations

The most reliable approach is maintaining an explicit allowlist of permitted redirect targets. Every redirect parameter is checked against this list before the redirect is issued.

ALLOWED_REDIRECTS = {
    "/dashboard",
    "/account/settings",
    "/projects",
}

def safe_redirect(requested_url):
    if requested_url in ALLOWED_REDIRECTS:
        return redirect(requested_url)
    return redirect("/dashboard")  # Safe default

When to use this: Applications with a finite, known set of post-login destinations. Most internal tools and admin panels fit this pattern.

Limitation: Does not scale when redirect targets are dynamic or user-generated.

Pattern 2: URL Validation with Origin Checks

When allowlisting individual paths is impractical, validate the URL structurally:

from urllib.parse import urlparse

ALLOWED_HOSTS = {"app.example.com", "docs.example.com"}

def validate_redirect(url):
    parsed = urlparse(url)

    # Reject any URL with a scheme other than HTTPS
    if parsed.scheme and parsed.scheme != "https":
        return False

    # If a host is present, it must be in the allowlist
    if parsed.netloc and parsed.netloc not in ALLOWED_HOSTS:
        return False

    # Reject URLs that contain authentication credentials
    if parsed.username or parsed.password:
        return False

    return True

Pattern 3: Relative-Path Enforcement

Force all redirect targets to be relative paths. This eliminates the possibility of redirecting to an external domain entirely.

def is_safe_relative_path(url):
    # Must not start with // (protocol-relative URL)
    if url.startswith("//"):
        return False

    # Must not contain a scheme
    if "://" in url:
        return False

    # Must start with /
    if not url.startswith("/"):
        return False

    # Must not contain backslashes (IE/Edge normalisation)
    if "\\" in url:
        return False

    return True

This is the recommended default for most web applications. If you can restrict redirects to relative paths on your own origin, do so.

Pattern 4: Signed Redirect Tokens

For flows where the redirect URL must be dynamic and external (such as OAuth), use signed tokens rather than raw URLs:

  1. Generate a redirect token containing the destination URL and a timestamp
  2. Sign it with an HMAC key known only to your server
  3. Pass the token as the redirect parameter instead of the raw URL
  4. On redirect, verify the signature and check the timestamp for freshness

This prevents tampering entirely. The attacker cannot forge a valid token without the signing key.

Common Bypass Techniques to Defend Against

Attackers have developed a substantial catalogue of bypass techniques. Your validation must account for all of them.

URL Parser Confusion

Different URL parsers interpret ambiguous URLs differently. An attacker exploits the gap between how your validation code parses the URL and how the browser interprets it.

https://[email protected]          # Userinfo section
https://example.com%40evil.com        # Encoded @
https://evil.com\.example.com         # Backslash normalisation
https://example.com#.evil.com         # Fragment confusion

Defence: Parse the URL with the same library the browser will use. Reject URLs containing @, \, or encoded variants of these characters in the host portion.

Protocol-Relative URLs

//evil.com/phish

The browser interprets this as https://evil.com/phish (or http://, matching the current page's protocol). Many validation routines only check for http:// or https:// prefixes and miss this.

Defence: Explicitly reject URLs starting with //.

Data and JavaScript URIs

data:text/html,<script>alert(1)</script>
javascript:alert(1)

These do not involve a network request but execute in the context of the redirecting origin.

Defence: Allowlist schemes explicitly. Only permit https (and http if genuinely needed). Reject everything else.

Double Encoding

https://example.com/redirect?url=https%253A%252F%252Fevil.com

If your application decodes the URL parameter twice, the first decode produces https%3A%2F%2Fevil.com, and the second produces https://evil.com.

Defence: Decode exactly once. Validate after decoding. If the decoded value still contains percent-encoded characters, reject it.

Open Redirect Chaining

An attacker finds an open redirect on trusted-partner.com and chains it through your OAuth flow:

https://your-idp.com/authorize?redirect_uri=https://your-app.com/callback?next=https://trusted-partner.com/redirect?url=https://evil.com

Defence: Validate the final destination, not just the immediate redirect target. Where possible, avoid passing through user-controlled redirect parameters at all.

Implementation Checklist

Use this checklist when auditing redirect handling in your applications:

  • [ ] Inventory all redirect endpoints — search codebase for redirect(, Location:, 301, 302, 307, 308, window.location, meta http-equiv="refresh"
  • [ ] Classify each redirect — static destination, relative path, or dynamic/user-controlled
  • [ ] Apply relative-path enforcement to every redirect that does not require an absolute URL
  • [ ] Implement an allowlist for redirects that must use absolute URLs
  • [ ] Enforce exact-match redirect_uri validation in all OAuth/OIDC implementations
  • [ ] Reject URLs containing @, \\, and non-HTTPS schemes in redirect parameters
  • [ ] Decode URL parameters exactly once before validation
  • [ ] Log rejected redirect attempts for monitoring and incident response
  • [ ] Add CSP navigate-to directive (where browser support permits) as a defence-in-depth measure
  • [ ] Review third-party libraries for redirect behaviour — middleware, authentication libraries, and CMS plugins often introduce redirect endpoints you did not write

Verification Steps

Manual Testing

  1. Basic open redirect test: Append ?return_to=https://evil.example to every redirect endpoint. Confirm the application does not follow the redirect.

  2. Protocol-relative test: Try ?return_to=//evil.example. Confirm rejection.

  3. Encoded bypass test: Try ?return_to=https%3A%2F%2Fevil.example and ?return_to=https%253A%252F%252Fevil.example. Confirm both are rejected or redirect to a safe default.

  4. Userinfo bypass test: Try ?return_to=https://[email protected]. Confirm rejection.

  5. JavaScript URI test: Try ?return_to=javascript:alert(document.domain). Confirm rejection.

Automated Scanning

Use tools that specifically test for open redirects:

# Nuclei template for open redirect detection
nuclei -t http/vulnerabilities/open-redirect/ -u https://your-app.com

# Burp Suite active scan with redirect checks enabled
# Configure scope to include all redirect parameters

OAuth-Specific Verification

  1. Register a client with redirect URI https://app.example.com/callback
  2. Attempt authorisation with redirect_uri=https://app.example.com/callback/../evil
  3. Attempt authorisation with redirect_uri=https://evil.example.com
  4. Attempt authorisation with redirect_uri=https://app.example.com/callback?next=https://evil.example.com
  5. Confirm all four attempts are rejected by the authorization server

Gotchas

Framework defaults are not always safe. Django's LoginView validates next parameters against ALLOWED_HOSTS, but only if ALLOWED_HOSTS is properly configured. A wildcard * in ALLOWED_HOSTS disables redirect validation entirely.

JavaScript single-page applications often handle redirects client-side with window.location.href = params.get('redirect'). This is just as dangerous as a server-side redirect. Apply the same validation.

CDN and edge-function redirects (Cloudflare Workers, AWS Lambda@Edge) may implement redirect logic outside your main application. Audit these separately — they are easy to overlook.

Mobile deep links (myapp://) in redirect parameters can trigger unintended application launches. If your application only serves web content, restrict redirect schemes to https exclusively.

Meta refresh tags and JavaScript redirects bypass server-side redirect validation entirely if injected through other vulnerabilities (XSS). Open redirect prevention is one layer; it does not substitute for output encoding and XSS prevention.

Monitoring and Response

Log every redirect that is rejected by your validation logic. Include:

  • Timestamp
  • Source IP
  • The rejected URL value
  • The endpoint that received the request
  • The authenticated user (if any)

A sudden spike in rejected redirects indicates active probing. Correlate with your security monitoring and operations alerting to catch campaigns early.

Set up alerts for patterns such as:

  • More than 10 rejected redirects from a single IP within 5 minutes
  • Rejected redirects containing domains associated with known phishing infrastructure
  • Rejected redirects on OAuth endpoints specifically

Further Reading