Login Return URLs and Open Redirects: Securing next, returnUrl, and Callback Parameters

Login and SSO flows often carry a return destination. Learn how unsafe next, returnUrl, redirect, and callback parameters become open redirects, and how to constrain them safely.

Back to Blog

Login Return URLs and Open Redirects: Securing next, returnUrl, and Callback Parameters

A login flow often needs to remember where the user was going. Someone opens /billing, gets sent to /login, signs in, and returns to billing. The simplest implementation is to put the destination in a query parameter such as next, returnUrl, or redirect.

That convenience becomes a security problem when the application accepts an arbitrary external URL and redirects the browser there after login. Attackers can then build links on a trusted domain that end on a phishing site, or abuse callback logic in authentication workflows.

The fix is not “block http://.” URL parsing has too many edge cases for string tricks. The safer approach is to decide which destinations the application actually needs, represent them in a constrained form, and validate them with a real URL parser or server-side mapping.

Why return URLs exist

Return destinations improve user experience. They are common in:

  • login and logout flows;
  • password reset completion;
  • account activation;
  • checkout or subscription flows;
  • SSO;
  • OAuth authorization;
  • partner portals;
  • marketing redirects.

The security issue appears when user-controlled input is treated as an instruction about where the browser should go next.

The basic open redirect pattern

An unsafe implementation might do this:

app.get('/login/complete', (req, res) => {
  res.redirect(req.query.next || '/dashboard');
});

If next accepts any absolute URL, an attacker may be able to create a trusted-looking link that eventually sends the victim to an external site.

OWASP recommends avoiding user-controlled full destination URLs where possible. A server-side ID or short token mapped to an approved location is safer.

Prefer internal paths over arbitrary URLs

If the application only needs to return users to pages on the same origin, accept an internal path rather than a full URL.

A safer Node.js-style helper can parse the value relative to the expected origin and reject anything that escapes it:

const ALLOWED_PATHS = new Set([
  '/dashboard',
  '/billing',
  '/settings'
]);

function safeReturnPath(input) {
  if (!input) return '/dashboard';

  const candidate = new URL(input, 'https://app.example.com');
  if (candidate.origin !== 'https://app.example.com') {
    return '/dashboard';
  }

  if (!ALLOWED_PATHS.has(candidate.pathname)) {
    return '/dashboard';
  }

  return candidate.pathname + candidate.search;
}

The allowlist is intentionally narrow. If your application has hundreds of legitimate destinations, a signed or server-side return token may scale better.

Server-side return tokens are easier to reason about

Instead of passing a raw destination through the browser, store or sign a short-lived reference:

// Conceptual example
const token = issueReturnToken({ path: '/billing', expiresIn: '10m' });
res.redirect('/login?return_token=' + encodeURIComponent(token));

After login, verify the token and recover the approved internal path. The user never controls an arbitrary host.

Tokens still need integrity, expiry, and careful validation. The advantage is that the application defines the destination rather than trying to sanitize an attacker-supplied URL.

Watch for protocol-relative and parser edge cases

Weak validators often check for strings beginning with http:// or https:// and allow everything else. That misses forms such as protocol-relative URLs beginning with //, encoded variants, backslash behavior in some parsers, and confusing user-info syntax.

Do not build a home-grown URL parser with regular expressions. Use the platform’s URL parser, compare the normalized origin to an expected value, and allow only destinations you genuinely need.

“Relative” does not mean safe by itself

A relative path is easier to constrain than an arbitrary absolute URL, but it still needs validation. For example, an application may have an internal redirector route such as /go?url=.... Allowing /go without considering its behavior can reintroduce the external redirect indirectly.

This is why allowlists should represent trusted business destinations, not merely strings that begin with a slash.

OAuth redirect_uri is a different but related problem

OAuth and OpenID Connect use registered redirect URIs to return authorization responses to clients. That parameter is not simply a convenient post-login destination. It is part of the protocol’s trust model.

Clients should register precise redirect URIs and authorization servers should validate them according to the protocol and provider requirements. Do not apply a loose “same company domain” rule and assume that is equivalent to exact client redirect registration.

Separately, the client application may still have its own post-login next or returnUrl. Keep those two decisions distinct.

OAuth state does not validate the destination

The OAuth state parameter is commonly used to bind requests and protect against CSRF-like authorization response mixups. It can also carry application state when designed carefully. It does not make an arbitrary redirect URI safe.

If you encode return information in state, protect its integrity and validate the recovered destination just as you would any other return path.

A company application has this route:

https://portal.example.com/login?next=https://attacker.example/reset

The user sees the legitimate company domain, signs in through SSO, and then lands on the external page. The external page looks like the company’s “session expired” screen and asks for credentials again.

The open redirect did not steal the password by itself. It made the phishing flow more believable because the journey began on the real portal and happened immediately after a legitimate login.

The fix is to stop accepting arbitrary external values. The application changes next to an internal path allowlist and uses a server-side token for the few dynamic flows that need it.

Logout redirects matter too

Logout endpoints often accept a destination so users can return to a public home page or identity provider. Treat them with the same care. A trusted-domain logout link that forwards to an attacker-controlled site can still be useful for phishing.

For federated logout, follow the identity provider’s supported post-logout redirect registration rather than inventing a general-purpose URL parameter.

Return parameters can also appear after password reset or email verification. Be especially cautious here because the user is already in a security-sensitive workflow and may trust the next page more readily.

A reset token should never be forwarded to an external destination through a redirect chain or exposed in a referrer. Keep completion destinations controlled and remove sensitive query material before loading unnecessary third-party resources.

How to test return URL handling safely

On applications you own or are authorized to test:

  • identify parameters such as next, return, returnUrl, redirect, continue, and callback;
  • use a harmless external destination such as https://example.com;
  • test absolute, protocol-relative, and encoded forms;
  • test login, logout, reset, activation, and partner flows separately;
  • check both server-side 3xx responses and client-side JavaScript redirects;
  • record the exact route that accepts the destination.

Vulnify’s Open Redirect Checker probes common redirect parameters and authentication paths using safe external examples. Its remediation guide includes allowlist and opaque-token patterns for follow-up.

What about redirect chains?

An individually safe redirect can still participate in a confusing chain. After hardening the destination logic, inspect the full path from entry point to final page. Vulnify’s Redirect Chain Checker can help review loops, downgrade behavior, and canonical redirect efficiency, while the Open Redirect Checker focuses on attacker-controlled destinations.

Centralize redirect validation

Do not let every controller invent its own rules. Create one return-path helper used by login, logout, reset, and account workflows. Centralization reduces the chance that one route accepts a broader destination format than the others.

Tests for that helper should include:

  • approved internal paths;
  • unapproved internal paths;
  • absolute external URLs;
  • protocol-relative URLs;
  • encoded separators;
  • unexpected schemes;
  • nested redirector routes.

Framework defaults are useful, but they are not your business policy

Many web frameworks provide helpers that reject obviously external redirect targets or normalize paths. Use them, but still define the destinations your application actually needs. A generic “same host” check can be too broad if the host contains internal redirector routes, legacy campaign endpoints, or paths that hand control to another application.

Review return-path handling when authentication architecture changes. Moving from local login to SSO, adding a new identity provider, introducing a reverse proxy, or splitting the application across subdomains can invalidate assumptions that were safe when the code was first written.

Do not reflect rejected destinations carelessly

When validation rejects an external destination, avoid echoing the complete untrusted URL into an HTML error page without appropriate output encoding. A redirect flaw and an injection flaw are separate issues, but the same attacker-controlled parameter can reach both code paths if error handling is careless.

A simple fallback such as “Unable to continue to that destination” is usually enough. Keep detailed rejected URL information in controlled logs rather than rendering it back to the user.

Log rejected destinations without leaking secrets

Repeated attempts to send users to external destinations can be useful security telemetry. Log normalized destination details and route context, but avoid storing password reset tokens, authorization codes, or other secrets in logs.

Return URL security checklist

  • Full external URLs avoided where not needed?
  • Internal paths parsed and allowlisted?
  • Protocol-relative URLs rejected?
  • OAuth redirect URIs registered precisely?
  • state not treated as redirect validation?
  • Login, logout, reset, and activation flows reviewed?
  • Nested internal redirectors considered?
  • Validation centralized?
  • External probes retested after remediation?

Make the destination a business decision, not user input

Return URLs are easiest to secure when the application chooses where a workflow may end. If the browser needs to carry state, use constrained internal paths or short-lived server-controlled references rather than accepting arbitrary destinations.

That design is easier to explain, test, and maintain. It also removes a useful phishing primitive from a part of the application where users are already primed to trust what happens next.