A login flow often accepts a destination to visit after authentication. Checking whether that value starts with a familiar hostname is insufficient: a different hostname can contain the same prefix, and a protocol-relative URL can select a different host. Parse the candidate, compare its normalized origin, and return the validated absolute URL.

The Node.js URL reference documents the WHATWG URL constructor, origin, username, and password properties. Origin includes scheme and host information but excludes credentials, so this example checks credentials separately. The base URL is a fixed application setting rather than a request-supplied Host header.

Prerequisites

Use a supported Node.js release with ES modules. Save this program as redirect.mjs. It uses only built-in APIs and performs no network requests. The example.test names are fixture destinations.

import assert from 'node:assert/strict';

const base = new URL('https://app.example.test/account/');
function localRedirect(candidate) {
  if (typeof candidate !== 'string' || candidate.length > 2048 ||
      /[\u0000-\u0020\u007f]/u.test(candidate)) {
    throw new Error('invalid redirect');
  }
  const url = new URL(candidate, base);
  if (url.origin !== base.origin || url.username || url.password) {
    throw new Error('external redirect');
  }
  return url.href;
}
assert.equal(localRedirect('/help?from=login#top'), 'https://app.example.test/help?from=login#top');
assert.equal(localRedirect('../home'), 'https://app.example.test/home');
assert.equal(localRedirect('https://app.example.test:443/ok'), 'https://app.example.test/ok');
for (const bad of ['//evil.example/', 'https://app.example.test.evil.example/',
  'http://app.example.test/', 'javascript:alert(1)',
  'https://[email protected]/', '/bad\nheader', 'https://[']) {
  assert.throws(() => localRedirect(bad));
}
assert.equal(localRedirect('//app.example.test//evil.example/'),
  'https://app.example.test//evil.example/');
console.log('accepted:', localRedirect('../home'));
console.log('rejected redirect fixtures: 7');

Run and expected output

node redirect.mjs
accepted: https://app.example.test/home
rejected redirect fixtures: 7

Understand the accepted cases

The root-relative help destination preserves its query and fragment. A parent-relative home destination resolves against the fixed account base. The explicit default HTTPS port normalizes to the same origin. These assertions exercise parsed URL behavior rather than manual string comparisons.

The double-slash path regression is especially useful. A same-origin absolute URL can have a pathname beginning with two slashes. Returning only that pathname would let a later redirect consumer reinterpret it as a protocol-relative external destination. Returning url.href keeps the validated origin attached to the destination.

Understand rejection

The negative fixtures cover an external protocol-relative destination, a hostname with a misleading prefix, an HTTP scheme downgrade, a JavaScript scheme, embedded credentials, a literal newline, and malformed URL syntax. The application also imposes a candidate-length cap and rejects literal ASCII whitespace and control characters before parsing.

Use the returned value directly as the destination through your framework's redirect API. Do not decode, concatenate, or reinterpret it afterward. If the application only permits certain routes, add an explicit pathname allowlist as a separate policy; same-origin checking permits every path on that origin.

Boundaries and troubleshooting

This helper is a redirect-destination policy, not a server-side URL-fetch policy. It performs no DNS resolution and does not authorize access to a resource. A permitted local route could itself redirect elsewhere, so review redirect chains when integrating it into a real application.

If a legitimate input fails, inspect the parsed origin and the fixed base configuration using synthetic data. Do not relax the comparison to a hostname substring check. Browser navigation, response headers, proxy configuration, and framework behavior were not exercised by these local tests.

Continue with the node.js tutorials and Javascript tutorials.