Self-host: resume the MCP OAuth authorize after login#1233
Merged
Conversation
Better Auth's MCP authorize bounces an unauthenticated client to /login carrying the original OAuth request as query params, with no returnTo. The login page only honored returnTo, so after sign-in it landed on the dashboard and the authorization never resumed: the MCP client waiting at its callback never got its code. After sign-in, if the URL carries an MCP authorize request, hand it back to /api/auth/mcp/authorize so the now authenticated request issues a code (and the consent shim takes over). The target is the same-origin authorize endpoint, which validates the client and redirect_uri, so it is not an open redirect.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 4103ecb | Jun 30 2026, 11:46 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 4103ecb | Commit Preview URL Branch Preview URL |
Jun 30 2026, 11:45 PM |
Comment on lines
+68
to
+72
| // response_type alone is not enough: client_id and redirect_uri are required. | ||
| expect(mcpAuthorizeResumeTarget("?response_type=code&client_id=abc")).toBeNull(); | ||
| expect( | ||
| mcpAuthorizeResumeTarget("?response_type=token&client_id=abc&redirect_uri=x"), | ||
| ).toBeNull(); |
There was a problem hiding this comment.
The test comment says "client_id and redirect_uri are required" but only exercises the direction where
redirect_uri is absent — the symmetric case of redirect_uri present but client_id absent is not covered. Adding it makes the required-fields contract explicit in both directions.
Suggested change
| // response_type alone is not enough: client_id and redirect_uri are required. | |
| expect(mcpAuthorizeResumeTarget("?response_type=code&client_id=abc")).toBeNull(); | |
| expect( | |
| mcpAuthorizeResumeTarget("?response_type=token&client_id=abc&redirect_uri=x"), | |
| ).toBeNull(); | |
| // response_type alone is not enough: client_id and redirect_uri are required. | |
| expect(mcpAuthorizeResumeTarget("?response_type=code&client_id=abc")).toBeNull(); | |
| expect(mcpAuthorizeResumeTarget("?response_type=code&redirect_uri=x")).toBeNull(); | |
| expect( | |
| mcpAuthorizeResumeTarget("?response_type=token&client_id=abc&redirect_uri=x"), | |
| ).toBeNull(); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Contributor
Cloudflare previewTorn down — the PR is closed. |
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When an MCP client connects to a self-host instance and the user is signed out, Better Auth's
mcpauthorize 302s to/login?response_type=code&client_id=...with the original OAuth params (noreturnTo).login.tsxonly honored areturnToquery param, so after sign-in it sent the user to/and the authorize never resumed. The MCP client waiting at its loopback callback never received a code, so the connection silently failed.This is distinct from the integration OAuth callback recovery, which arrives as
returnTo; the MCP authorize to login to resume path was never wired through the login page. The e2e suite did not catch it because it signs in before authorizing, skipping this path.Fix
mcpAuthorizeResumeTarget(search)inreturn-to.ts: a pure helper that rebuilds/api/auth/mcp/authorize?...from the OAuth params Better Auth left on/login. The target is the same-origin authorize endpoint, which validatesclient_id/redirect_uri, so it is not an open redirect.login.tsx: after sign-in, resume the authorize if the URL carries one, else honor a safereturnTo, else land on the dashboard.Tests
return-to.test.tscoversmcpAuthorizeResumeTarget(rebuilds the authorize URL from a real redirect, ignores non-authorize searches).