MCP Authorization Explained: How MCP Uses OAuth 2.1 · Highflame
Highflame Identity is now open source: agent identity on open standards. Read the launch
Reference

MCP Authorization

MCP authorization is how a Model Context Protocol client gets permission to call a protected MCP server. For HTTP transports the spec builds on OAuth 2.1: the MCP server is a resource server, a separate authorization server issues audience-bound access tokens, and the client obtains them through a PKCE-protected authorization-code flow. Servers running over STDIO skip this and read credentials from the environment.

MCP authorization is optional. A server can be wide open, and many local ones are. But the moment a Model Context Protocol server needs to know who is calling, the spec defines exactly one way to do it for HTTP transports: OAuth 2.1. (Servers that run over STDIO skip all of this and read credentials from the environment instead.) This guide walks the flow the MCP authorization spec defines, in plain terms.

The three roles

OAuth splits the job across three parties, and MCP maps onto them directly:

  • The MCP server is the resource server. It holds the protected tools and data, and it checks the access token on every request.
  • The MCP client (Claude Code, Cursor, an agent) is the OAuth client. It obtains a token and attaches it to each call.
  • The authorization server interacts with the user and issues the access token. It can be hosted with the MCP server or be a completely separate service, such as your corporate identity provider.

The key idea: the MCP server never issues its own tokens or handles your password. It only validates tokens that its authorization server minted specifically for it.

Finding the authorization server

A client usually does not know, up front, who authorizes a given server. It finds out by asking. The first unauthenticated request comes back 401 Unauthorized with a WWW-Authenticate header pointing at the server’s Protected Resource Metadata (RFC 9728). That metadata names the authorization_servers. The client then fetches the authorization server’s own metadata (RFC 8414, or OpenID Connect Discovery) to learn its endpoints and confirm it supports PKCE. MCP servers must publish protected-resource metadata, so this discovery step always has something to find.

FIG 1 · AUTHORIZATION SERVER DISCOVERY 401 → PROTECTED RESOURCE METADATA → AS METADATA
MCP Client MCP Server resource server Auth Server MCP request (no token) 401 + WWW-Authenticate read resource_metadata URL GET protected resource metadata metadata: authorization_servers GET authorization server metadata AS metadata (endpoints, PKCE support) client now knows where to authorize

Getting a token: the PKCE code flow

With the authorization server located, the client runs a standard OAuth 2.1 authorization-code flow, hardened with PKCE. It generates a one-time secret (the PKCE verifier and its S256 challenge), opens a browser to the authorization server, and includes a resource parameter naming the exact MCP server it wants the token for. The user authorizes in the browser, the authorization server redirects back with a short-lived authorization code, and the client exchanges that code plus its PKCE verifier for an access token. From then on the client sends Authorization: Bearer <token> on every MCP request, and the server validates that the token was issued for it before doing anything.

FIG 2 · AUTHORIZATION CODE FLOW WITH PKCE SIGN IN → AUDIENCE-BOUND ACCESS TOKEN
Browser MCP Client MCP Server resource server Auth Server MCP request (no token) 401 + WWW-Authenticate get resource metadata authorization_servers get AS metadata (verify PKCE support) AS metadata generate PKCE + resource param open authorize URL authorization request + resource user authorizes redirect with authorization code authorization code token request + code_verifier + resource access token (+ refresh token) MCP request + Bearer token audience valid → MCP response

Identifying the client without pre-registration

One MCP-specific wrinkle: a client and a server usually have no prior relationship, so how does the server know which client is asking? MCP supports three answers, in priority order. A pre-registered client ID when the two already know each other. Client ID Metadata Documents (CIMD), where the client’s client_id is an HTTPS URL pointing at its own metadata JSON, for the common no-relationship case. And Dynamic Client Registration (RFC 7591), kept mainly for backwards compatibility.

CIMD is the one worth understanding, because it is what lets a brand-new client connect to a server it has never met without anyone provisioning credentials first. The client’s client_id is just a URL like https://app.example.com/client.json, and that URL serves a small JSON document listing the client’s name and allowed redirect URIs. When the authorization server sees a URL-shaped client_id, it fetches that document, validates that the client_id inside matches the URL exactly and that the redirect URI is on the allowed list, and shows the user the client’s name on the consent screen. Because the server is fetching an attacker-suppliable URL, it must guard against SSRF, which is why authorization servers often allowlist trusted domains.

FIG 3 · CLIENT ID METADATA DOCUMENTS IDENTIFY A CLIENT WITH NO PRIOR RELATIONSHIP
User MCP Client Auth Server Client Metadata HTTPS URL MCP Server connect to MCP server authorize (client_id = URL) detects URL client_id GET client_id URL client metadata JSON validate client_id + redirect_uri consent (shows client_name) approve authorization code exchange code + client_id access token MCP request + token MCP response

The rules that make it safe

Most of MCP authorization’s security lives in a handful of requirements:

  • Audience-bound tokens. The client must send the resource parameter (RFC 8707) naming the target server, and the server must reject any token not issued for it. This is what stops one server’s token being replayed at another.
  • No token passthrough. A server must not forward the client’s token to an upstream API. If it calls upstream, it acts as its own OAuth client with a separate token. Passthrough is the confused-deputy bug the spec explicitly forbids.
  • PKCE is mandatory. Clients must use S256 and must refuse to proceed unless the authorization server advertises PKCE support, closing authorization-code interception.
  • Exact redirect URIs and state. Redirect URIs are matched exactly against pre-registered values, must be HTTPS or localhost, and clients verify state, closing open-redirect and phishing paths.
  • Short-lived tokens. Authorization servers should issue short-lived access tokens and must rotate refresh tokens for public clients, to limit the damage of a leaked one.

Where MCP authorization stops

Notice what all of this decides: whether a token is valid, and which server it is good for. It does not decide whether a given tool call, with these arguments, right now, should be allowed. OAuth scopes are coarse; they gate which server a token works at, not the individual action. That runtime, per-call authorization is a layer the spec deliberately leaves open, the same gap Enterprise Managed Authorization leaves after it decides admission. MCP authorization is necessary and not sufficient: pair it with server hardening and per-action policy that catch what a valid token can still be steered into doing.

Highflame implements the spec at the door and closes that gap inside. Its open-source identity engine, ZeroID, can act as the MCP authorization server, minting audience-bound tokens; the gateway then validates token audience on every call, refuses token passthrough, brokers a separate upstream credential, and authorizes each individual tool call against one policy. See build vs buy for who should own that layer, and the platform for how it fits together.

Frequently asked questions

Does MCP require OAuth?
No. Authorization is optional in MCP. But when a server over an HTTP transport needs it, the spec says implementations SHOULD follow this OAuth 2.1-based flow. Servers over STDIO do not use it and instead read credentials from the environment.
What are the roles in MCP authorization?
Three. The MCP server is the OAuth 2.1 resource server (it validates tokens). The MCP client is the OAuth client (it obtains and sends tokens). A separate authorization server interacts with the user and issues the access tokens; it may be co-hosted with the MCP server or be a different service such as a corporate IdP.
How does an MCP client find the authorization server?
Through RFC 9728 Protected Resource Metadata. The client's first unauthenticated request returns 401 with a WWW-Authenticate header pointing at the server's resource-metadata URL. That metadata lists the authorization_servers, and the client then fetches the authorization server's own metadata (RFC 8414 or OpenID Connect Discovery) to learn its endpoints.
Why does MCP require PKCE?
PKCE prevents authorization-code interception and injection. Clients MUST use the S256 code challenge method and MUST verify, from the authorization server's metadata, that PKCE is supported before proceeding; if the server does not advertise it, the client must refuse to continue.
What is the resource parameter for?
It binds the token to one server. Under RFC 8707, the client MUST include a resource parameter naming the exact MCP server in both the authorization and token requests, and the server MUST validate that a token was issued for it. This is what stops a token from being replayed at a different service.
What is token passthrough, and is it allowed?
Token passthrough is a server forwarding the client's token to an upstream API. The spec forbids it. If an MCP server calls an upstream service it must act as its own OAuth client with a separate token, and it must reject any token whose audience is not itself. Passthrough creates the confused-deputy problem.
What is a Client ID Metadata Document (CIMD)?
CIMD is how an MCP client identifies itself when it has no prior relationship with the server. The client's client_id is an HTTPS URL that hosts a JSON document describing the client (its name and allowed redirect URIs). The authorization server fetches that URL, checks that the client_id inside matches the URL exactly, validates the redirect URI, and shows the client's name on the consent screen. It is MCP's preferred client-registration approach, ahead of dynamic client registration, and lets a brand-new client connect without pre-provisioned credentials.

See agent governance against your own agents.