Open redirection and SSRF prevention in Node.js and PHP
Open redirection and SSRF risks are common in dynamic redirects. Learn how to secure your Node.js and PHP apps using strict URL validation and whitelisting.
During a recent refactor of a legacy Node.js authentication service, I found a classic "redirect_to" parameter being passed directly into a res.redirect() call. It seemed harmless until I realized an attacker could swap that URL for a malicious site, turning our trusted domain into a vehicle for phishing.
Open redirection is a deceptively simple vulnerability. It happens when your application accepts a user-controlled input to decide where to send the browser, without verifying the destination. While I’ve touched on preventing open redirect vulnerabilities before, the intersection of these redirects with Server-Side Request Forgery (SSRF) makes the stakes much higher.
Why URL validation is harder than it looks
My first attempt at a fix was a simple startsWith('https://myapp.com') check. That failed immediately when I realized an attacker could use https://myapp.com.attacker.com. The string matched, but the destination was entirely external.
When you're handling dynamic redirects, you aren't just protecting the browser; you're often protecting your server's outbound network. If your backend follows a redirect to fetch metadata or a profile image, an unvalidated URL allows an attacker to force your server to probe internal network services, leading to SSRF.
Implementing secure URL validation
To secure your application, you need to stop relying on regex hacks and start using proper URL parsing. In Node.js, the URL constructor is your best friend. In PHP, use parse_url().
Here is the strategy I use to prevent both open redirection and SSRF:
- Whitelisting: Never use a blacklist. Maintain a list of allowed hostnames or domains.
- Strict Parsing: Break the URL into components and validate the host explicitly.
- Internal-only check: If the redirect is intended for internal use, ensure it doesn't contain a protocol or host that points outside your infrastructure.
Node.js Example
Using the native URL API, we can safely decompose the input:
JAVASCRIPTconst { URL } = require(CE9178">'url'); function isSafeRedirect(targetUrl, allowedHosts) { try { const parsed = new URL(targetUrl, CE9178">'https://myapp.com'); // Base URL handles relative paths return allowedHosts.includes(parsed.hostname); } catch (e) { return false; } }
PHP Example
PHP’s parse_url() is great, but watch out for edge cases where the host might be missing or malformed.
PHPfunction isSafeRedirect($targetUrl, $allowedHosts) { $parsed = parse_url($targetUrl); if (!$parsed || !isset($parsed['host'])) { return true; #6A9955">// Likely a relative path, handle accordingly } return in_array($parsed['host'], $allowedHosts); }
Comparing mitigation strategies
When securing your traffic, the approach you take depends on whether you are handling user-facing redirects or server-side requests.
| Strategy | Effectiveness | Complexity | Best Use Case |
|---|---|---|---|
| Regex Matching | Low | Low | Never recommended |
| Domain Whitelisting | High | Medium | User-facing redirects |
| Proxy/Gateway Routing | Very High | High | Complex microservices |
| Allow-list + Hardcoding | Absolute | Low | Fixed internal paths |
SSRF: The hidden danger of redirects
If your code fetches data from the URL provided by the user, you are at risk of SSRF. I’ve seen developers fall into this trap while building "link preview" features. Even if you validate the URL, a request might be redirected again by the target server.
I always recommend setting a strict timeout on your HTTP client (like axios or Guzzle) and disabling automatic follow-redirects. If you must follow them, inspect the new URL against your whitelist at every hop. This is similar to how we manage preventing arbitrary file write vulnerabilities by strictly controlling the destination of data streams.
Frequently Asked Questions
Is it enough to just check if the URL starts with /?
No. An attacker can use //evil.com, which browsers interpret as a protocol-relative URL, effectively redirecting to an external site. Always use a proper parser.
How do I handle relative paths safely?
If you only want to allow relative paths, ensure the parsed URL’s host property is null or empty before allowing the redirect.
Should I use a library for URL validation?
Native tools like URL in Node and parse_url in PHP are usually sufficient if used correctly. Avoid heavy dependencies unless you need complex validation logic.
Final thoughts
Securing redirects is less about perfect code and more about defensive configuration. I usually prefer a "hardcoded lookup" approach whenever possible—if the user needs to return to a page, provide an ID or a token in the URL instead of the full destination path.
I'm still refining how we handle these redirects in highly distributed systems where services share hostnames. The best advice I can give is to treat every URL input as untrusted, regardless of where it originates in your application.