Skip to content

fix: proxy /oauth/* through the BFF and stop guessing redirect_uri - #101

Open
marekdano wants to merge 1 commit into
mainfrom
6458-oauth-callback-path
Open

fix: proxy /oauth/* through the BFF and stop guessing redirect_uri#101
marekdano wants to merge 1 commit into
mainfrom
6458-oauth-callback-path

Conversation

@marekdano

Copy link
Copy Markdown
Contributor

Fixes #6458

Summary

The OAuth authorization-code popup had no reliable home. Two root causes:

  • No /oauth/* proxy on the BFF. The popup's first hop (window.open("/oauth/authorize/{id}?popup=true")) is a raw browser navigation with no /api prefix, so it fell through to the SPA's catch-all 404 handler instead of reaching the gateway - the popup opened the React shell instead of redirecting to the provider.
  • redirect_uri derived from window.location.origin. The web UI's own origin isn't necessarily where the gateway serves /oauth/callback in a split deployment, so the value registered with the OAuth provider could silently diverge from what the gateway/APP_DOMAIN actually use.

Changes

  • server/src/routes/proxy/oauth-authorize.ts (new): authenticated proxy for the popup's first hop — injects the session's bearer token (same as catch-all.ts does for /api/*), forwards the provider redirect untouched, and rejects cross-site requests via the same isForbiddenCrossOrigin guard login.ts/proxy-sse.ts already use (this route isn't idempotent — it can run DCR registration and DB writes - and can't rely on a CSRF token since window.open sets no headers).
  • server/src/routes/proxy/oauth-callback.ts (new): unauthenticated proxy for the second hop, needed both for gateways with a pre-existing redirect_uri pointing at the web UI's origin, and so the flow works when the gateway isn't independently internet-reachable (common split deployment).
  • server/src/lib/oauth-upstream-forward.ts (new): shared fetch/timeout/error/header-forwarding logic between the two proxy routes.
  • src/components/mcp-servers/OAuth2Auth.tsx: stopped deriving/submitting redirect_uri from window.location.origin. When no value is stored, the form now shows an explicit "determined automatically by the server" placeholder instead of a guess, letting the gateway's own APP_DOMAIN-based default apply.
  • Removed the now-dead onRedirectUriChange prop chain through AdvancedSettings.tsx/MCPServerForm.tsx.
  • New i18n strings (en-US/es-ES/pt-BR) for the placeholder/help text.
  • Tests: BFF proxy routes (oauth-authorize.test.ts, oauth-callback.test.ts, including a cross-origin rejection test), updated OAuth2Auth.test.tsx, and a new e2e/oauth-authorization.spec.ts driving the full popup flow through a real browser (success and error paths) with the popup's network mocked at the browser-context level.

Test plan

  • npm run typecheck (root + server/)
  • npm run lint
  • npx vitest run — 95 BFF + 3304 frontend tests passing
  • npx playwright test — new OAuth specs + full servers.spec.ts passing
  • Manually verified end-to-end against a real GitHub OAuth App, including a split-origin deployment (see below) — confirmed working

Testing locally (split-origin deployment)

The bug only reproduces when the web UI and the gateway are on genuinely different origins, so localhost for both isn't enough. Using /etc/hosts aliases avoids needing a second device or exposing anything beyond loopback:

  1. Add two aliases (sudo required):
    sudo sh -c 'echo "127.0.0.1 web.local" >> /etc/hosts'
    sudo sh -c 'echo "127.0.0.1 api.local" >> /etc/hosts'
  2. mcp-context-forge/.env — the gateway defaults to binding 127.0.0.1 only, so it must be opened up to accept requests addressed toapi.local:
    HOST=0.0.0.0
    APP_DOMAIN=http://api.local:8000
  3. contextforge-web-ui/.env — for local HTTP testing:
    COOKIE_SECURE=false
    (CONTEXTFORGE_URL can stay http://127.0.0.1:8000 — that hop is server-to-server, same machine, origin doesn't matter there.)
  4. Restart both, then open the UI at http://web.local:3000not localhost:3000 — so window.location.origin genuinely differs from APP_DOMAIN.
  5. For a gateway using GitHub (or any OAuth App with a single registered callback URL): update the OAuth App's Authorization callback URL to match http://api.local:8000/oauth/callback, and delete/recreate the gateway in the UI so redirect_uri starts unset (picks up the new APP_DOMAIN - based default rather than a stale value from before this fix).
  6. Trigger the OAuth connect flow — the popup should reach the provider, redirect back to api.local:8000/oauth/callback, and close itself with a success notification in the opener.

Signed-off-by: Marek Dano <mk.dano@gmail.com>
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.

[BUG]: OAuth browser flow has no agreed origin — /oauth/* unproxied by the BFF and redirect URI derived twice

1 participant