From ce39302df3a3db6c79c103a33714d67a6d8ab4f9 Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Wed, 10 Jun 2026 12:03:52 -0500 Subject: [PATCH 1/3] docs: validate state.returnTo before navigating The "Passing Data Through Auth Flows" example showed `window.location.href = state.returnTo` with no validation. Since `state` round-trips through the redirect as plaintext in the URL and is not validated by WorkOS, an unvalidated `returnTo` is an open-redirect / `javascript:`-URI sink. Replace the example with an origin-resolve + same-origin check and add a warning that `state` contents are untrusted. --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c107ae3..33b562d 100644 --- a/README.md +++ b/README.md @@ -234,8 +234,15 @@ Use `state` to preserve data across the authentication redirect: { - if (state?.returnTo) { - window.location.href = state.returnTo; + if (typeof state?.returnTo !== "string") return; + // `state` round-trips through the redirect as plaintext in the URL and is + // not validated by WorkOS, so treat it as untrusted input. Resolve it + // against your own origin and only navigate if it stays same-origin; this + // rejects absolute URLs (https://evil.com), protocol-relative URLs + // (//evil.com), and javascript: URIs in one step. + const url = new URL(state.returnTo, window.location.origin); + if (url.origin === window.location.origin) { + window.location.href = url.pathname + url.search + url.hash; } }} > @@ -243,6 +250,13 @@ Use `state` to preserve data across the authentication redirect: ``` +> [!WARNING] +> Anything you read from `state` is **untrusted user input**. It travels as +> plaintext in the redirect URL and WorkOS cannot validate its contents. Before +> using a `state.returnTo`-style value to navigate, validate it as shown above — +> never pass it directly to `window.location.href`, which would execute a +> `javascript:` URI or follow an open-redirect to an attacker's site. + ### Handling Token Refresh Failures ```jsx From 1850436907193c9fa74a70e8509819dadc1d50ee Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:33:51 +0000 Subject: [PATCH 2/3] docs: guard URL parsing against malformed state.returnTo --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33b562d..a61727a 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,12 @@ Use `state` to preserve data across the authentication redirect: // against your own origin and only navigate if it stays same-origin; this // rejects absolute URLs (https://evil.com), protocol-relative URLs // (//evil.com), and javascript: URIs in one step. - const url = new URL(state.returnTo, window.location.origin); + let url; + try { + url = new URL(state.returnTo, window.location.origin); + } catch { + return; + } if (url.origin === window.location.origin) { window.location.href = url.pathname + url.search + url.hash; } From 0a0d3d5341e384db4d22881cfa42dd0fecb8211a Mon Sep 17 00:00:00 2001 From: Nick Nisi Date: Thu, 11 Jun 2026 13:27:52 -0500 Subject: [PATCH 3/3] docs: navigate to validated url.href, not reconstructed pathname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building on the try/catch guard: reconstructing from url.pathname can yield a protocol-relative path (e.g. returnTo "https://your-app//evil.com" → pathname "//evil.com") that redirects off-site even though the origin check passed. Navigating to the origin-validated url.href avoids this. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a61727a..2aa73f1 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,11 @@ Use `state` to preserve data across the authentication redirect: return; } if (url.origin === window.location.origin) { - window.location.href = url.pathname + url.search + url.hash; + // Navigate to the origin-validated absolute URL. Don't rebuild from + // url.pathname: a value like "https://your-app//evil.com" passes the + // origin check but has pathname "//evil.com", which is protocol-relative + // and would redirect off-site. + window.location.href = url.href; } }} >