Connector OAuth: persistent attempts, completed only by the user who started - #832
Merged
Merged
Conversation
…started - Pending authorizations move from an in-memory map to connector_oauth_attempts (state hashed, verifier and client credentials encrypted, 15 min, single use), so a restart or blue/green deploy between consent and callback no longer loses them. - The provider callback no longer exchanges the code: it checks the state and forwards code + state to /connectors/oauth/complete, which posts them to POST /api/mcp-oauth/complete. The code is exchanged only for the user who started the flow; anyone else gets 403 and the attempt is spent. Before, a consent link started on one person's connector could be completed by someone else, handing over that person's tokens. - Provider errors (access_denied, invalid_scope, invalid_client) are explained on the complete page, with a way back to the connector. - authorize accepts an internal returnTo; GET /api/connectors/oauth/redirect-uri returns the redirect URI from SERVER_URL, used by the connector form instead of guessing it from the browser's location.
This was referenced Oct 3, 2026
keysersoft
enabled auto-merge (squash)
October 3, 2026 12:05
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Second step of the connector setup plan, and a prerequisite for the guided install: the "Authorize with Provider" flow becomes durable and tied to the person who started it.
Pending authorizations are persisted. They lived in an in-memory
Map(10 min), so any restart or blue/green deploy between consent and callback lost them, and a second instance would never see them. New tableconnector_oauth_attempts:statestored as SHA-256;ENCRYPTION_KEY;takePendingFlowdeletes as it reads).The code is exchanged only for the user who started the flow.
GET /api/mcp-oauth/callbackno longer exchanges anything. It checks that the state is one we issued and redirects to/connectors/oauth/complete?state&code.POST /api/mcp-oauth/complete(JWT). The server compares the caller with the attempt'suserId; anyone else gets 403 and the attempt is spent.Smaller pieces
access_denied,invalid_scope,invalid_client…) are explained on the complete page, with a link back to the connector.authorizeaccepts an internalreturnTo(absolute URLs,//hostand backslashes are dropped), for the guided install that comes next.GET /api/connectors/oauth/redirect-urireturns the redirect URI fromSERVER_URL. The connector form shows it instead of guessing fromwindow.location(wrong behind a proxy).Tests
prisma migrate diffreports no drift, and a round trip through the real table works (hashed state, encrypted payload, 15 min TTL, gone after use).Deploy note: an authorization started before the deploy and completed after it fails with "expired"; the user starts it again.