feat: support OAuth 2.1 / MCP login flow#10
Open
acoshift wants to merge 5 commits into
Open
Conversation
Add the pieces an MCP CLI client needs to authenticate against this service as an OAuth 2.1 authorization server, while keeping the existing confidential web-client flow working. - discovery metadata (RFC 8414) at /.well-known/oauth-authorization-server - PKCE (S256) on the authorize and token endpoints - public clients: /token accepts code_verifier instead of client_secret - Dynamic Client Registration (RFC 7591) at /register - token introspection (RFC 7662) at /introspect for the resource server - loopback redirect URIs (RFC 8252) for native/CLI clients - standard token response (access_token + expires_in; refresh_token kept for backward compatibility) - BASE_URL / INTROSPECTION_TOKEN env vars; periodic cleanup of expired oauth2 sessions and codes Flows are discriminated by client token_endpoint_auth_method: existing rows default to client_secret_post (unchanged); registered MCP clients are public (none) and PKCE-gated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the first tests to the repo, using go-sqlmock to fake the DB layer (pgctx talks to a standard *sql.DB, no transactions) and an httptest stub for Google's token endpoint. Regression (old flow): - TokenHandler confidential client_secret path still returns refresh_token (and now access_token/expires_in), wrong/missing secret rejected - RedirectHandler glob redirect validation + Google redirect + session cookie - CallbackHandler session lookup, state check, internal code issuance New flow: - TokenHandler public/PKCE: success, bad verifier, redirect mismatch, missing verifier, unsupported grant_type, unknown client, invalid code - RedirectHandler public: PKCE required, S256 only, loopback redirect match - CallbackHandler carries PKCE/redirect/resource onto the code - Dynamic Client Registration validation + success - Introspection: config/auth gates, active/unknown/empty token - Discovery metadata; PKCE + redirect + registration URI helpers To make the callback testable, googleTokenURL is now a package var. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow the ../dropbox pattern: a schema package embeds the SQL migrations (01_init pre-MCP + user_tokens, 02_mcp the PR's ALTERs) and tu.Setup starts an isolated in-memory cockroach-go/v2 testserver per test, applying the migration. Tests seed via pgctx and assert against real DB state (token persisted, code consumed, session PKCE carried through), which the sqlmock version could not verify. As a bonus the suite exercises the MCP migration SQL on a real CockroachDB. Google's token endpoint is stubbed via a shared httptest server keyed by the auth code (parallel-safe), set once through the googleTokenURL package var. Drops the go-sqlmock dependency; adds cockroach-go/v2 (test only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch oauth2_clients.redirect_uris from a newline-delimited string to a native CockroachDB string[] array. Reads scan straight into the []string (pgsql wraps slice destinations with pq.Array); writes pass pq.Array. Drops the strings join/split in oauth2.go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Turns this service into an OAuth 2.1 authorization server that an MCP CLI client can authenticate against, while keeping the existing confidential web-client flow working unchanged. Flows are discriminated by the client's
token_endpoint_auth_method: existing rows default toclient_secret_post(unchanged); registered MCP clients are public (none) and PKCE-gated.What's added
GET /.well-known/oauth-authorization-server/tokenacceptscode_verifierinstead ofclient_secretPOST /registerPOST /introspectfor the (separate) resource server, guarded byINTROSPECTION_TOKENaccess_token+expires_in;refresh_tokenretained for backward compatibilityBASE_URL/INTROSPECTION_TOKENenv vars; periodic cleanup of expiredoauth2_sessions/oauth2_codesDatabase migration
Apply against the existing database before deploying:
Tests
First tests in the repo. They run against a real CockroachDB (
cockroach-go/v2testserver per test, schema applied from the embeddedschema/*.sqlmigrations) and stub Google's token endpoint via thegoogleTokenURLpackage var — covering both the old confidential flow (regression) and the new public/PKCE flow, DCR, and introspection.go test ./...,go vet ./...,gofmt -lall clean.Review notes
token_typecase change — the legacy/tokenresponse used"token_type":"bearer"; it now returns"Bearer"(the RFC-registered value).refresh_tokenis preserved. If the existing web client comparestoken_typecase-sensitively, this needs a revert to lowercase.resource(RFC 8707) is stored but not enforced — issued tokens are the existing opaque 7-day tokens, not audience-bound. Full audience restriction would need per-resource tokens (larger change).401+WWW-Authenticatepointing at/.well-known/oauth-protected-resource, serve that metadata, and validate tokens via/introspect.Test plan
go build ./...,go vet ./...,gofmt -lcleango test ./...) cover old confidential + new PKCE flows, DCR, introspectioncurl $BASE_URL/.well-known/oauth-authorization-serverclaude mcp add --transport http …🤖 Generated with Claude Code