Open Redirect: How Attackers Hijack Your Login Flow
Attackers use your trusted domain to send users to phishing sites
Open redirect happens when your app redirects users to any URL they specify in a parameter. Attackers craft links using your trusted domain that redirect to phishing sites. Always validate redirect URLs against an allowlist of trusted destinations.
What is open redirect?
Open redirect (CWE-601) happens when your application blindly redirects users to any URL specified in a request parameter. Users see your familiar domain and trust the link, but after clicking, they end up on an attacker's phishing site.
Think of it like a receptionist at a trusted company who gives visitors directions to wherever someone asks - including to a scam office down the street. The receptionist (your app) doesn't verify the destination, just redirects.
Why is this dangerous? Users trust your domain. They see the SSL lock, recognize the URL, and click. By the time they realize they're on a fake site that looks just like yours, they've already entered their credentials.
Why open redirects are dangerous
Phishing with Trusted Domains
A link like https://yourapp.com/login?returnUrl=https://evil-clone.com looks legitimate. Users trust it because it starts with your domain. After logging in on your real site, they're redirected to the attacker's identical-looking phishing page.
OAuth Token Theft
In OAuth flows, if attackers control the redirect destination, they can steal authorization codes or tokens. The victim authenticates legitimately, then the token lands in the attacker's hands.
Malware Distribution
Open redirects can send users to malware-hosting sites. Drive-by downloads become more effective when the initial link appears to come from a trusted source.
Vulnerability Chaining
Open redirects can enhance SSRF attacks by providing redirects to internal URLs, or enable XSS via javascript: protocol URLs.
Where AI tools create open redirects
When you ask AI tools to "redirect after login" or "add a return URL," they generate the simplest working code without validation. This is a classic vibe coding trap - the code works perfectly until an attacker finds it.
Common vibe coded patterns that create open redirects:
- Login flows - "Return to original page after login"
- OAuth callbacks - "Redirect after successful authentication"
- Action completions - "Redirect after form submission"
- Email links - "Track clicks and redirect to destination"
- Short URLs - "Create redirect service"
Common open redirect patterns
Attackers have various techniques to exploit and bypass weak validation:
Query Parameter
/redirect?url=https://evil.com The classic pattern. URL in query string is used directly.
Protocol-Relative
/redirect?url=//evil.com Bypasses checks for "http" because it starts with //.
JavaScript Protocol
/redirect?url=javascript:alert(1) Can execute code if the redirect becomes href.
URL Encoding
/redirect?url=%2f%2fevil.com Encoded // bypasses simple string checks.
Subdomain Tricks
/redirect?url=https://trusted.com.evil.com Looks like your domain but isn't.
Backslash Confusion
/redirect?url=/\evil.com Some parsers treat backslash as path separator.
For detailed exploitation techniques, see the PortSwigger Open Redirect Guide.
Vulnerable code examples
Pattern 1: Vulnerable Return URL (AI Default)
// AI generates this for "return after login"
app.get('/login', (req, res) => {
const returnUrl = req.query.returnUrl || '/'
// ... authenticate user ...
res.redirect(returnUrl) // DANGEROUS!
})
// Attacker: /login?returnUrl=https://evil-phishing-site.com
// User sees your trusted login, then lands on phishing pageAny URL accepted as redirect target. The attacker crafts links that look legitimate but redirect to phishing.
Pattern 2: Vulnerable OAuth Callback
// OAuth callback without validation
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query
// ... exchange code for token ...
const redirectTo = req.query.redirect || '/dashboard'
res.redirect(redirectTo) // DANGEROUS!
})
// Attacker: /auth/callback?code=xxx&redirect=https://evil.com
// Token ends up exposed in attacker-controlled destinationOAuth flows are high-value targets. Without validation, attackers can steal tokens by controlling the redirect.
How to fix open redirects
Secure Pattern: Path Allowlist (Recommended)
// Validate against allowlist of paths
const ALLOWED_REDIRECTS = ['/', '/dashboard', '/profile', '/settings']
function isValidRedirect(url) {
// Must be a string
if (!url || typeof url !== 'string') return false
// Must start with / (relative path)
if (!url.startsWith('/')) return false
// Must NOT be protocol-relative (//evil.com)
if (url.startsWith('//')) return false
// Remove query string for comparison
const path = url.split('?')[0]
// Check against allowlist
return ALLOWED_REDIRECTS.some(allowed =>
path === allowed || path.startsWith(allowed + '/')
)
}
app.get('/login', (req, res) => {
const returnUrl = req.query.returnUrl || '/'
if (!isValidRedirect(returnUrl)) {
return res.redirect('/') // Default to safe page
}
// ... authenticate ...
res.redirect(returnUrl)
})Only allow specific, known paths. Reject external URLs, protocol-relative URLs, and unexpected paths.
Secure Pattern: Domain Validation
// Validate URL is on trusted domain
const TRUSTED_DOMAINS = ['myapp.com', 'app.myapp.com']
function isValidRedirectUrl(urlString) {
try {
// Parse with base URL to handle relative paths
const url = new URL(urlString, 'https://myapp.com')
// Must be trusted domain
if (!TRUSTED_DOMAINS.includes(url.hostname)) {
return false
}
// Block dangerous protocols
if (!['http:', 'https:'].includes(url.protocol)) {
return false
}
return true
} catch {
return false
}
}Parse URLs properly using the URL constructor. Validate hostname and protocol explicitly.
Most Secure: Mapping Approach
// Map IDs to URLs instead of accepting URLs directly
const REDIRECT_MAP = {
'dashboard': '/dashboard',
'profile': '/profile',
'settings': '/settings',
'home': '/',
}
app.get('/goto', (req, res) => {
const target = req.query.target
const redirectUrl = REDIRECT_MAP[target] || '/'
res.redirect(redirectUrl)
})
// Usage: /goto?target=dashboard
// Attacker cannot inject arbitrary URLsNever accept URLs directly. Map safe identifiers to hardcoded URLs. Impossible to exploit.
Key Security Points
- Allowlist, don't blocklist - specify exactly what's allowed
- Check for // - protocol-relative URLs bypass http checks
- Block javascript: and data: - can execute code
- Use URL parser - don't regex match URLs manually
- Consider mapping - IDs are more secure than URLs
AI fix prompt: Open redirect audit
Copy this prompt to your AI tool to scan your codebase for open redirect vulnerabilities:
You are a security auditor. Scan this codebase for Open Redirect vulnerabilities (CWE-601).
## What to Search For
### 1. Redirect Functions with User Input
Search for redirect operations that use request parameters:
- res.redirect(req.query.url)
- redirect(userInput)
- Response.redirect(urlFromRequest)
- window.location = userUrl
- router.push(userPath)
### 2. Common Parameter Names
Search for these parameter names in redirect context:
- returnUrl, returnTo, return_to, return
- redirectUrl, redirectTo, redirect_to, redirect
- next, nextUrl, next_url
- url, target, destination, dest
- callback, callbackUrl, callback_url
- goto, go, forward, continue
### 3. OAuth/Auth Callback Handlers
Look for OAuth callback endpoints:
- /auth/callback
- /api/auth/callback
- /oauth/callback
- /login/callback
- OAuth state parameter handling
## Validation Requirements
For any redirect, verify ALL of these checks exist:
```javascript
function isValidRedirect(url) {
// 1. Must start with / (relative path)
if (!url.startsWith('/')) return false
// 2. Must NOT be protocol-relative (//evil.com)
if (url.startsWith('//')) return false
// 3. Block dangerous protocols if full URL
try {
const parsed = new URL(url, 'https://example.com')
if (!['http:', 'https:'].includes(parsed.protocol)) return false
} catch {}
// 4. Allowlist check (preferred)
const basePath = url.split('?')[0]
const ALLOWED = ['/dashboard', '/profile', '/settings']
if (!ALLOWED.some(p => basePath.startsWith(p))) return false
return true
}
```
## Report Format
For each finding, report:
1. File and line number
2. The vulnerable code pattern
3. What parameter controls the redirect
4. What validation exists (if any)
5. Whether it's in auth/login flow (higher severity)
6. Specific fix with codeFrequently Asked Questions
What is an open redirect vulnerability?
Open redirect (CWE-601) happens when your application redirects users to any URL specified in a parameter without validation. Attackers craft malicious links using your trusted domain that redirect victims to phishing sites. Because the link starts with your legitimate domain, users trust it and click.
Why are open redirects dangerous?
Open redirects enable sophisticated phishing attacks. Users see your trusted domain and SSL certificate, so they click the link and enter credentials. They are then redirected to a fake site that looks like yours. Attackers can also use open redirects to deliver malware or chain with other vulnerabilities like SSRF.
How do I validate redirect URLs?
Use an allowlist of permitted redirect destinations. Only allow relative paths that start with "/" and match known routes in your app. Parse URLs properly using the URL constructor to prevent protocol tricks. Never accept arbitrary external URLs. For OAuth callbacks, validate the redirect_uri against registered values.
Can open redirects lead to account takeover?
Yes, especially in OAuth flows. If an attacker can control the redirect_uri or post-login redirect, they may be able to steal authorization codes or tokens. The victim authenticates on your real site, then gets redirected to the attacker who captures the token in the URL. Always validate OAuth callbacks.
How do I fix open redirect in Next.js?
In Next.js, validate the redirect path before using redirect() or res.redirect(). Check that the path starts with "/" but not "//". Validate against an allowlist of permitted paths. For API routes handling OAuth callbacks, strictly validate the redirect destination matches your configured callback URLs.
Related Security Topics
External Resources
- CWE-601: Open Redirect - Official vulnerability definition
- OWASP Redirect Cheat Sheet - Prevention recommendations
- PortSwigger Open Redirect - Detection and exploitation
- Snyk Open Redirect - Practical examples
Find Open Redirects in Your Code
VibeShip Scanner automatically detects unvalidated redirects in your codebase, including OAuth callback handlers and login return URLs.
Scan Your Code Free