Skip to content

Handle OAuth callback in different browser with retry guard#2404

Merged
gary149 merged 2 commits into
mainfrom
claude/pr-18-reapply-s27kso
Jul 2, 2026
Merged

Handle OAuth callback in different browser with retry guard#2404
gary149 merged 2 commits into
mainfrom
claude/pr-18-reapply-s27kso

Conversation

@gary149

@gary149 gary149 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

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-loginRetry cookie to prevent infinite redirect loops when restarting the OAuth flow

    • hasLoginRetryCookie(): Check if retry has already been attempted
    • setLoginRetryCookie(): Set one-shot guard cookie (5 minute expiry)
    • clearLoginRetryCookie(): Clear guard after successful login
  • Enhanced callback validation: Modified error handling in /login/callback to gracefully recover from cross-browser scenarios

    • When CSRF token or code verifier validation fails, check if this is a retry attempt
    • If not retried yet, redirect back to /login with the sanitized return path preserved
    • If already retried, throw 403 with appropriate error message
  • Exported 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 payload

Implementation Details

  • The retry cookie uses 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 observable
  • Return path recovery is best-effort with try-catch to handle malformed state tokens gracefully
  • Error messages distinguish between CSRF token and code verifier failures to aid debugging

https://claude.ai/code/session_01QWamsbC2xoGRoQazysMEVp

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/routes/login/callback/+server.ts Outdated
next = undefined;
}

return redirect(302, `${base}/login${next ? `?next=${encodeURIComponent(next)}` : ""}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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
@gary149 gary149 merged commit 39dca47 into main Jul 2, 2026
6 checks passed
@gary149 gary149 deleted the claude/pr-18-reapply-s27kso branch July 2, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants