# Laravel Open Redirect: How to Validate Redirect URLs and Prevent Phishing Attacks

> Redirects using unvalidated user input let attackers craft links that appear to come from your domain but redirect victims to malicious sites.

**Severity:** high | **Category:** Application Security

---

## The Issue

An open redirect occurs when your application redirects users to a URL taken from request input without validating that the URL is safe. Attackers exploit this by sending victims links like yourdomain.com/login?redirect=https://evil-site.com. Because the link starts with your trusted domain, users and email filters trust it. After login, the victim is redirected to the attacker's phishing page. This is commonly used in credential harvesting attacks.

## Steps to Fix

### 1. Validate redirect URLs against an allowlist

Never redirect to arbitrary user-supplied URLs:

// DANGEROUS
return redirect($request->input('redirect'));

// SAFE — validate against your own domain
$url = $request->input('redirect', '/');
if (!Str::startsWith($url, '/') || Str::startsWith($url, '//')) {
    $url = '/';
}
return redirect($url);

// SAFEST — use named routes
$allowed = ['dashboard', 'profile', 'settings'];
$route = $request->input('redirect', 'dashboard');
if (!in_array($route, $allowed)) {
    $route = 'dashboard';
}
return redirect()->route($route);

### 2. Use Laravel's intended() for post-login redirects

Laravel provides a built-in safe redirect mechanism for post-authentication flows:

return redirect()->intended('/dashboard');

This redirects to the URL the user originally requested (stored in the session by the auth middleware), falling back to /dashboard. Since the URL comes from the session (not user input), it cannot be manipulated by attackers.

### 3. Audit all redirect() calls that use request input

Search your codebase for potentially unsafe redirects:

grep -rn 'redirect(.*request\|redirect(.*\$_GET\|redirect(.*input' app/ routes/ --include='*.php'

Also check for:
- header('Location: ' . $userInput)
- Response::redirectTo($userInput)
- redirect()->away($userInput) — this explicitly bypasses domain validation

Every occurrence needs validation.

## Verification

Test with an external URL:

curl -v 'https://yourapp.com/login?redirect=https://evil-site.com'

After login, the redirect should go to your default page (like /dashboard), not to the external URL. Also test protocol-relative URLs:

curl -v 'https://yourapp.com/login?redirect=//evil-site.com'

Run php artisan stackshield:scan --check=SS041 to verify.

## Prevention

Only accept relative paths (starting with / but not //) for redirect parameters. Use Laravel's redirect()->intended() for auth flows. Never use redirect()->away() with user input. Add redirect validation to your code review checklist.

---

## Frequently Asked Questions

### Is Str::startsWith($url, '/') sufficient validation?

Not alone. The URL //evil-site.com starts with / but is a protocol-relative URL that redirects to an external domain. Always check for both: must start with / AND must not start with //.

### Can open redirects lead to more than phishing?

Yes. Open redirects can be chained with other vulnerabilities: OAuth token theft (redirect authorization codes to attacker), SSRF (server-side request forgery), and bypassing URL-based security controls. Many bug bounty programs accept open redirect reports.

### Does Laravel's redirect()->intended() prevent open redirects?

Yes. redirect()->intended() reads the intended URL from the session, which was stored by Laravel's auth middleware when the unauthenticated user first hit a protected route. Since the value comes from the session and not request input, attackers cannot inject an external URL through this mechanism. Always prefer redirect()->intended() over redirect($request->input("redirect")) for post-login flows.

### What is a protocol-relative URL and why is it dangerous in redirects?

A protocol-relative URL like //evil.com/fake starts with // rather than https://. Browsers treat this as an absolute URL using the current page's protocol, making it effectively https://evil.com/fake. A check that only verifies the URL starts with / would incorrectly pass this as a relative path. Always also check that the URL does not start with //.

### Should I validate the return_url parameter in OAuth and social login flows?

Yes. OAuth callbacks often encode a return URL in the state parameter. Validate that after a successful OAuth flow, the redirect destination is a relative path on your domain. Attackers have exploited OAuth return URL parameters to steal authorization codes from legitimate providers by injecting an open redirect into the flow.

