T14: /mcp behind workers-oauth-provider (DCR + consent) #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "t14-mcp-oauth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
T14:
/mcpbehind workers-oauth-provider (DCR, consent, grants bound to our users)Stacked on #12 (
t13-aggregates) → … → #1. Merge those first.Architecture
src/server.ts:OAuthProviderin front of TanStack Start.wrangler.jsoncmainnow points here./mcpwith a valid MCP access token → MCP handler./oauth/token,/oauth/register(dynamic client registration),/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource/mcp(RFC 9728) → the provider./authorize, Better Auth) → TanStack./authorizerequires the normal website session (FIT OAuth via Better Auth, T6). Anonymous users go to/login?next=<full authorize URL>and come back after the FIT login.completeAuthorizationwithuserId= ourusers.idand props{ userId }. MCP calls therefore act as the same user, with that user's encrypted upstream tokens.state. The single FIT callback (/auth/callback) stays Better Auth's.src/server/consent-page.ts, en/cs), built on the provider'sdescribeConsent/beginConsent/approveConsent/denyConsent:frame-ancestors 'none',default-src 'none', andform-action 'self'plus the validated redirect origin or scheme.X-Frame-Options: DENY. The POST requires the SameSite=Lax session.error,stateandiss. Unknown clients and bad redirect URIs are shown locally./mcp: the statelesscreateMcpHandlerfromagents/mcp/serverwith@modelcontextprotocol/serverv2, one server per request.allowedHostnamesis thePUBLIC_URLhost plus localhost. No tools yet; T15 addssearch/describe/read/call.safeNext()for/login?next: parsed like a browser would, same origin only, re-checked after normalisation.wrangler.jsonc:OAUTH_KVbinding (placeholder id; local only, no Cloudflare resources created).global_fetch_strictly_public, which restricts outbound fetch to the public internet (an SSRF guard) and enables Client ID Metadata Document clients.test/helpers/kv.ts),cloudflare:*stubs aliased invitest.config.tsfor theagentspackage, and the fake-FIT login moved totest/helpers/auth.ts(shared with the T6 tests).Verified
bun run lint && bun run typecheck && bun run test: 183 tests. 12 of them are new end-to-end MCP OAuth tests through the realOAuthProvider:WWW-Authenticatewithout a token; anonymous user →/login?next=…; the full register → FIT login → consent → code → token (PKCE) → MCPinitializechain. The grant is listed under ourusers.id, and KV holds no upstream tokens.access_denied; POST without a session, from a different browser, with a bad decision, or with a reused handle; user switched in another tab; tampered binding.form-actionsource;safeNextbypass vectors.bun run dev+ curl:/mcpwithout a token returns 401 with the right challenge./authorizesends anonymous users to/loginwith the fullnext, an unknown client gets 400, and a missing-PKCE request redirects withinvalid_request. Better Auth accepts/authorize?…as a post-logincallbackURL.Astra review (gpt-6-astra), 2 rounds
Fixed:
safeNext("/a/..//evil.example")normalised to//evil.example→ re-checked after serialisation.form-action 'self'would make Chromium block the post-consent redirect to the client → the validated redirect origin or scheme is allowed.Round 2: "No remaining concrete defects found."
🤖 Generated with Claude Code