[PR #9646] fix(mcp): register OAuth callback before opening browser #13173

Closed
opened 2026-02-16 18:18:02 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/9646

State: closed
Merged: Yes


Summary

Fix race condition where the OAuth callback arrives before the state is registered in pendingAuths map.

Fixes #9645

Problem

When using MCP servers with OAuth authentication (e.g., Keycloak), the OAuth flow fails with a CSRF error if the Identity Provider has an active SSO session:

No pending authentication for state: <state>

Root Cause

The current code flow is:

  1. open(authorizationUrl) - Opens browser
  2. McpOAuthCallback.waitForCallback(oauthState) - Registers callback handler

When the IdP has an active SSO session, the redirect back to the callback URL happens before step 2 completes. The callback handler receives the authorization code but can't find the state in pendingAuths, triggering the CSRF protection error.

Solution

Move waitForCallback() to before opening the browser:

// Register the callback BEFORE opening the browser to avoid race condition
const callbackPromise = McpOAuthCallback.waitForCallback(oauthState)

// Now open browser - even if redirect is instant, we're ready
await open(authorizationUrl)

// Wait for the already-registered promise
const code = await callbackPromise

Testing

  • Tested with Keycloak IdP that has active SSO session (fast redirect)
  • Tested with fresh authentication flow (slow redirect with user interaction)
  • Both scenarios now work correctly
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/9646 **State:** closed **Merged:** Yes --- ## Summary Fix race condition where the OAuth callback arrives before the state is registered in `pendingAuths` map. Fixes #9645 ## Problem When using MCP servers with OAuth authentication (e.g., Keycloak), the OAuth flow fails with a CSRF error if the Identity Provider has an active SSO session: ``` No pending authentication for state: <state> ``` ### Root Cause The current code flow is: 1. `open(authorizationUrl)` - Opens browser 2. `McpOAuthCallback.waitForCallback(oauthState)` - Registers callback handler When the IdP has an active SSO session, the redirect back to the callback URL happens **before** step 2 completes. The callback handler receives the authorization code but can't find the state in `pendingAuths`, triggering the CSRF protection error. ## Solution Move `waitForCallback()` to **before** opening the browser: ```typescript // Register the callback BEFORE opening the browser to avoid race condition const callbackPromise = McpOAuthCallback.waitForCallback(oauthState) // Now open browser - even if redirect is instant, we're ready await open(authorizationUrl) // Wait for the already-registered promise const code = await callbackPromise ``` ## Testing - Tested with Keycloak IdP that has active SSO session (fast redirect) - Tested with fresh authentication flow (slow redirect with user interaction) - Both scenarios now work correctly
yindo added the pull-request label 2026-02-16 18:18:02 -05:00
yindo closed this issue 2026-02-16 18:18:02 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#13173