Open Redirect in Express.js Applications
Express.js makes it trivial to redirect users with res.redirect(), but this simplicity creates security risks. When redirect targets come from query parameters, form fields, or request headers (like the Referer), attackers exploit the redirect to send users from your trusted domain to malicious sites.
Scan Your Express AppHow Open Redirect Manifests in Express
The most common open redirect in Express is the post-login redirect pattern: res.redirect(req.query.returnTo). This sends users to whatever URL the returnTo parameter contains, including external domains. OAuth implementations are particularly vulnerable. After receiving an authorization code, the app redirects to a URL from the state parameter or a stored redirect_uri. If these are not validated, attackers steal OAuth tokens by redirecting to their own server. Express apps that use res.redirect(req.headers.referer) to send users back to the previous page are exploitable — the Referer header is controlled by the attacker. URL shortener and link tracking features that store redirect destinations are also vectors. If the stored URL is not validated when created, every click on the short link becomes an open redirect.
Real-World Impact
An Express-based SaaS application had a /login?returnTo= parameter that redirected after authentication. An attacker crafted login links pointing to a credential-harvesting clone: https://app.com/login?returnTo=https://app-login.attacker.com. After users authenticated on the real site, they were redirected to the fake site which showed "session expired" and captured passwords. An Express app implementing OAuth had an open redirect that allowed attackers to intercept authorization codes. The attacker modified the state parameter to include their server URL, and after the user authorized the app, the authorization code was sent to the attacker instead of the legitimate callback.
Step-by-Step Fix
Create a safe redirect helper
Build a utility that validates redirect destinations and blocks external URLs.
// lib/safe-redirect.ts
export function getSafeRedirectPath(
input: string | undefined,
fallback = '/'
): string {
if (!input) return fallback;
// Must be a relative path
if (!input.startsWith('/') || input.startsWith('//')) {
return fallback;
}
// Block backslash tricks
if (input.includes('\\')) return fallback;
try {
const url = new URL(input, 'http://placeholder.local');
if (url.hostname !== 'placeholder.local') return fallback;
return url.pathname + url.search;
} catch {
return fallback;
}
}Apply safe redirects in login flows
Use the safe redirect helper in all authentication redirect logic.
import express from 'express';
import { getSafeRedirectPath } from './lib/safe-redirect';
const app = express();
// UNSAFE
app.post('/login', (req, res) => {
// ... authenticate ...
res.redirect(req.query.returnTo as string || '/');
});
// SAFE
app.post('/login', (req, res) => {
// ... authenticate ...
const destination = getSafeRedirectPath(
req.query.returnTo as string,
'/dashboard'
);
res.redirect(destination);
});Validate OAuth redirect URIs
For OAuth flows, validate redirect_uri against a list of pre-registered URIs.
const ALLOWED_REDIRECT_URIS = new Set([
'https://app.example.com/auth/callback',
'https://staging.example.com/auth/callback',
'http://localhost:3000/auth/callback', // dev only
]);
app.get('/oauth/authorize', (req, res) => {
const redirectUri = req.query.redirect_uri as string;
if (!ALLOWED_REDIRECT_URIS.has(redirectUri)) {
return res.status(400).json({
error: 'invalid_redirect_uri',
message: 'The redirect_uri is not registered.',
});
}
// Proceed with OAuth flow...
});Prevention Best Practices
1. Never pass user input directly to res.redirect(). Validate all redirect targets. 2. Only allow relative paths for redirect destinations, not absolute URLs. 3. Maintain an allowlist of permitted external redirect domains if external redirects are required. 4. Validate OAuth redirect_uri parameters against registered URIs. 5. Never redirect based on the Referer header without validation.
How to Test
1. Search for res.redirect() calls in your codebase and check if any use user input directly. 2. Test /login?returnTo=https://evil.com and verify the app does not redirect externally. 3. Try //evil.com, /\evil.com, and /\\evil.com as redirect targets. 4. Check OAuth callback handlers for unvalidated redirect_uri parameters. 5. Use Vibe App Scanner to automatically detect open redirect vulnerabilities in your Express application.
Frequently Asked Questions
Is res.redirect() safe to use in Express?
res.redirect() itself is not vulnerable — the vulnerability is in passing unvalidated user input as the redirect target. Always validate the destination before calling res.redirect(). Use relative paths and reject any input that could be interpreted as an external URL.
Can open redirects be exploited even with HTTPS?
Yes. Open redirects have nothing to do with HTTPS. The attack exploits the trust users and systems place in your domain name. HTTPS ensures the connection is encrypted but does not prevent the server from redirecting to a malicious destination.
How do open redirects interact with OAuth security?
Open redirects can be chained with OAuth flows to steal authorization codes. If your app has an open redirect and is also an OAuth redirect_uri target, an attacker can modify the OAuth flow to redirect the authorization code through your app's open redirect to their server. This is why OAuth 2.0 requires exact redirect_uri matching.
Related Security Resources
Is Your Express App Vulnerable to Open Redirect?
VAS automatically scans for open redirect vulnerabilities in Express applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Express stack.