Handle OAuth callback in different browser with retry guard#2404
Conversation
When the OIDC callback lands in a browser that didn't start the login flow (e.g. "Open in Safari" from an in-app browser), both the CSRF state validation and the PKCE code verifier cookie check fail, and the user dead-ends on a 403. Restart the login flow once in the new browser instead, guarded by a short-lived one-shot cookie to prevent redirect loops, and best-effort preserve the original return path (re-sanitized) via the login route's ?next= param. Re-applies gary149/chat-ui#18 which had targeted the wrong repository. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWamsbC2xoGRoQazysMEVp
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a8b420858
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| next = undefined; | ||
| } | ||
|
|
||
| return redirect(302, `${base}/login${next ? `?next=${encodeURIComponent(next)}` : ""}`); |
There was a problem hiding this comment.
Preserve alternate callback when retrying OAuth
When the original flow was started as /login?callback=<allowed alt redirect>, this retry sends the browser back to /login with only next. triggerOauthFlow only selects an alternate redirect URI from the callback query parameter (auth.ts lines 557-560), so the restarted authorization request falls back to ${url.origin}${base}/login/callback; deployments relying on ALTERNATIVE_REDIRECT_URLS then hit the provider with an unregistered or wrong redirect URI instead of recovering. Carry a whitelisted redirectUrl from the state back as callback when restarting.
Useful? React with 👍 / 👎.
The retry redirect previously restarted the flow with only ?next=, so logins started with an ALTERNATIVE_REDIRECT_URLS callback lost that redirect URI. Carry the state payload's redirectUrl back as ?callback= when it matches the allowlist (triggerOauthFlow re-validates it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWamsbC2xoGRoQazysMEVp
Summary
Improves OAuth login flow resilience when the callback is received in a different browser than the one that initiated the login (e.g., "Open in Safari" from an in-app browser). Instead of failing with a 403 error, the flow now restarts in the current browser while preserving the intended return path.
Key Changes
Added retry guard mechanism: Introduced
hfChat-loginRetrycookie to prevent infinite redirect loops when restarting the OAuth flowhasLoginRetryCookie(): Check if retry has already been attemptedsetLoginRetryCookie(): Set one-shot guard cookie (5 minute expiry)clearLoginRetryCookie(): Clear guard after successful loginEnhanced callback validation: Modified error handling in
/login/callbackto gracefully recover from cross-browser scenarios/loginwith the sanitized return path preservedExported
sanitizeReturnPath(): Made the path sanitization function public so it can be reused in the callback handler to recover the intended destination from the unverified state payloadImplementation Details
sameSite: "lax"(even when strict is configured) to ensure it's sent on the cross-site IdP → callback navigation where the loop guard must be observablehttps://claude.ai/code/session_01QWamsbC2xoGRoQazysMEVp