Learn to stop open redirect vulnerabilities by validating destination URLs. Protect your Node.js and PHP apps from phishing attacks with these practical tips.
During a recent audit of a legacy codebase, I found a login controller that was essentially a one-click ticket for a phishing campaign. The application accepted a ?next= parameter to redirect users after authentication, but it didn't bother checking where that parameter actually pointed. It was a classic open redirect. By simply changing the URL, an attacker could send unsuspecting users to a pixel-perfect clone of our login page hosted on a different domain.
If you’re handling redirects based on user input, you’re holding a loaded gun. It’s easy to fix, but it requires a shift in how you think about untrusted data.
An open redirect happens when your application takes a user-provided URL and redirects the browser to it without validation. Attackers love these because they look legitimate. If a user sees https://your-app.com/login?next=https://malicious-site.com, they might trust the first part of the URL and ignore the second.
We’ve seen this lead to credential harvesting and drive-by downloads. The fix isn't to stop using redirects, but to enforce strict input validation on the destination. Before you touch security, make sure you've also addressed other entry points like preventing mass assignment vulnerabilities with DTOs in Laravel and Express to ensure your data layer stays clean.
My first instinct years ago was to use a regex to check if the URL started with /. It failed almost immediately. Attackers can bypass simple starts-with checks using double slashes (//malicious.com), which browsers often interpret as a protocol-relative URL.
Never trust a string just because it looks like a relative path. If you aren't careful, you’ll end up with vulnerabilities similar to those I’ve documented when preventing path traversal in Node.js and PHP.
In Node.js, we should use the built-in URL constructor to parse the input. If the URL is absolute, we compare the hostname against a whitelist of allowed domains. If it’s relative, we ensure it starts with a single /.
JAVASCRIPTconst { URL } = require(CE9178">'url'); function isSafeRedirect(url, allowedHost) { try { // Check for protocol-relative bypasses if (url.startsWith(CE9178">'//')) return false; // If itCE9178">'s a relative path, it's usually safe if (url.startsWith(CE9178">'/')) return true; // If it's an absolute URL, validate the host const parsed = new URL(url); return parsed.hostname === allowedHost; } catch (e) { return false; } }
This approach forces us to be explicit. If you're building out your auth flow, remember that preventing session fixation is just as critical as redirect security.
PHP’s parse_url function is your best friend here. Don't try to roll your own validation with strpos.
PHPfunction is_safe_redirect($url, $allowed_host) { $parsed = parse_url($url); #6A9955">// Relative paths have no host if (!isset($parsed['host'])) { return str_starts_with($url, '/'); } #6A9955">// Absolute URLs must match our domain return $parsed['host'] === $allowed_host; }
The logic here is roughly 5 lines of code, yet it prevents a high-severity phishing vector. I’ve seen developers try to strip characters or use blocklists, but that’s a losing game. Always use an allowlist approach.
?next=https://external.com, pass an ID like ?next=dashboard. Map that ID to a URL on your server side. This is the most robust way to prevent an open redirect.Regex is fragile. Attackers can use different encodings, whitespace, or protocol-relative URLs (e.g., //example.com) to bypass simple filters. Always use a proper URL parsing library.
It depends. If your subdomains are user-generated or hosted by third parties, an attacker might be able to host content there. Only redirect to subdomains you explicitly trust and control.
Use a configuration file or an environment variable that defines a strict whitelist. Never build a redirect system that allows arbitrary domains passed via a URL parameter.
I’m still not a fan of allowing arbitrary redirects. If I were refactoring a large system today, I’d push for a "redirect service" that only supports a predefined map of paths. It removes the dynamic nature of the input entirely. We often focus on complex threats, but sometimes the most effective security is just saying "no" to user input that doesn't strictly follow the rules. Keep your validation logic simple, test your edge cases, and stop passing around raw URLs.
Master XXE prevention by hardening your XML parsers in PHP and Node.js. Learn the specific flags and settings needed to stop unauthorized data access.
Read moreLearn how to prevent session fixation by properly regenerating session IDs during login. Secure your Node.js and Laravel apps with these battle-tested tips.