import {
DocsTitle,
DocsLead,
DocsH2,
DocsP,
DocsList,
DocsLi,
DocsCode,
Mono,
} from '@/components/docs-page';
export const metadata = { title: 'OAuth 2.1 flow — BuildMyMCPServer docs' };
export default function OAuthDocs() {
return (
<>
OAuth 2.1 flow
Every generated server is an OAuth 2.1 Resource Server. The control plane is the
Authorization Server. Dynamic Client Registration, PKCE, and Resource Indicators per the
2025 MCP authorization spec.
Standards we follow
OAuth 2.1 draft (draft-ietf-oauth-v2-1) — no implicit, mandatory PKCE
RFC 8414 — Authorization Server Metadata at /.well-known/oauth-authorization-server
RFC 9728 — Protected Resource Metadata at /.well-known/oauth-protected-resource
RFC 8707 — Resource Indicators (audience binding)
RFC 7591 — Dynamic Client Registration
End-to-end walkthrough
First request from a fresh client to a fresh server is unauthenticated. The server
replies with a 401 plus a WWW-Authenticate header pointing to
its resource metadata.
The client fetches that resource metadata, sees the authorization server, then fetches the
AS metadata to discover registration, authorize, token and JWKS endpoints.
The client registers itself dynamically. No human in the loop, no preconfigured client
IDs. Each AI surface gets its own ephemeral identity.
Authorization Code with PKCE. The user gives consent, the AS returns a one-time code,
the client exchanges it for an RS256-signed JWT bound to the resource (audience).
Subsequent /mcp calls carry the JWT. The runner verifies the signature
against the AS's JWKS, checks the iss, the aud
(RFC 8707 — must match the runner's own public URL), and the expiry. No token
passthrough; the runner never forwards the client's token to a downstream API.
Why this matters
Without audience binding, a token issued for one customer's MCP server could be
replayed against another customer's server. RFC 8707 closes that. Without PKCE, a
public OAuth client on a desktop is exposed to interception of the authorization code.
OAuth 2.1 closes that.
>
);
}