Hand-roll a minimal OAuth 2.1 authorization server for MCP
Decision
Build our own OAuth 2.1 authorization server and implement only what the MCP authorization spec requires. We support the authorization code grant with mandatory PKCE, S256 only (RFC 7636). Dynamic client registration (RFC 7591) is rate-limited per IP. We publish authorization server metadata and protected resource metadata (RFC 9728). The user signs in by passkey or password (ADR-0058) and approves a consent screen. Refresh tokens are single-use and rotate on every exchange.
Access tokens come from the existing PASETO token service (ADR-0028), so an MCP client carries the same kind of token as the app, with its OAuth client_id in the device-id claim. All OAuth state lives in Redis with a TTL: ten minutes for an authorization code, 90 days for a client registration or refresh token. The server adds no Postgres tables for it.
Why
No Rust crate covers this combination. oxide-auth comes closest and handles the authorization code grant with PKCE, but it lacks dynamic client registration and resource metadata, and its token model fights issuing PASETO tokens. openid and oauth2-rs are client libraries.
The build-versus-buy trade-off has moved now that an LLM can produce a working implementation of an RFC in one pass. Owning the code costs much less than it did, so a small implementation we have checked line by line against the RFCs beats bending a crate to fit. An integration suite is the safety net. It walks the whole flow from registration through sign-in, consent, code exchange, API access, refresh and refresh-token replay.
Rejected alternatives
- oxide-auth, extended to fit. We would fork or wrap it to add registration, resource metadata and a custom token format, and then carry that divergence against upstream.
- A hosted identity provider such as Auth0, or a self-hosted server such as Ory Hydra. Either issues its own tokens, so an MCP client would carry a different credential from the app's PASETO token, and it puts another vendor or service on the sign-in path.
Consequences
We own every line and every bug, and no upstream project ships us security fixes. No crate stands in the way when the MCP spec or our token rules change, and each spec revision is ours to implement.