Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,38 @@ Use `state` to preserve data across the authentication redirect:
<AuthKitProvider
clientId="client_01ABC123DEF456"
onRedirectCallback={({ state }) => {
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.
let url;
try {
url = new URL(state.returnTo, window.location.origin);
} catch {
return;
}
if (url.origin === window.location.origin) {
// 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;
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}}
>
<App />
</AuthKitProvider>
```

> [!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
Expand Down
Loading