What is MCP authorization? How OAuth works for AI agents
MCP authorization is the OAuth 2.1 flow that lets an AI agent call a protected MCP server on a user's behalf. Here is how it works, step by step, under the 2026-07-28 spec.
MCP authorization is the OAuth 2.1 flow that lets an AI agent call a protected MCP server on behalf of a user, without ever holding that user's password or a long-lived API key. In it, three roles map cleanly onto OAuth: the MCP server is the resource server, the MCP client (the agent or its host application) is the OAuth client, and a separate authorization server handles the user interaction and issues short-lived, audience-bound access tokens scoped to that one MCP server.
This article describes the 2026-07-28 revision of the MCP specification, which is current as of September 2026. If you have read about MCP auth before, the change most published guides have not caught up with is that Dynamic Client Registration is now deprecated in favor of Client ID Metadata Documents. Two requirements that are not new but are still widely omitted: the resource parameter from RFC 8707 (a MUST on both authorization and token requests since 2025-06-18) and audience validation on the server side.
Key takeaways
- Authorization in MCP is OPTIONAL and transport-dependent. HTTP-based servers should implement it; STDIO servers should not, and take credentials from the environment instead.
- The MCP server is an OAuth 2.1 resource server, not an authorization server. It validates tokens; it does not issue them. Discovery is how the client finds out who does.
- Tokens are bound to one audience. A client MUST send
resource(RFC 8707) identifying the target server, and the server MUST reject any token not issued for itself. This is what stops a token minted for one MCP server from being spent at another. - Client registration no longer needs a registration endpoint. Under Client ID Metadata Documents (CIMD), the
client_idis an HTTPS URL that serves the client's metadata as JSON. DCR is deprecated and retained only for backwards compatibility. - Permissions escalate mid-task via step-up authorization. A
403witherror="insufficient_scope"tells the agent exactly which scopes to go get, and the client must request the union of old and new scopes so it doesn't lose what it already had.
MCP authorization vs. MCP authentication
These get used interchangeably and shouldn't be. Authentication establishes who is calling. Authorization establishes what that caller may do. MCP's authorization spec covers the handshake that produces a scoped access token (the identity question) and then stops.
What it deliberately does not cover is the decision that happens after a valid token arrives: whether this caller may invoke delete_repository on that resource. The spec gives you the scope as a coarse-grained input to that decision and leaves the policy to you. In practice that gap is where most of the design work ends up, not in the handshake.
How the OAuth roles map to MCP
The important structural point: the MCP server and the authorization server are distinct roles, even when one deployment happens to play both. Earlier revisions treated the MCP server as its own authorization server; the 2025-06-18 revision reclassified MCP servers as OAuth resource servers, and that is the model today. A modern MCP server delegates to an authorization server and confines itself to validation.
When does MCP authorization apply?
Only to HTTP transports. The spec is explicit:
- HTTP-based transports SHOULD conform to the authorization spec.
- STDIO transports SHOULD NOT. A local server launched as a subprocess retrieves credentials from its environment. An API key in an env var is the correct pattern there, not OAuth.
- Other transports MUST follow security best practices for their protocol.
So if you're running a local MCP server on your laptop and wondering why you never encountered an OAuth flow: you're on STDIO, and you're not supposed to.
How MCP authorization works, step by step
The full flow, in the order it actually executes:
1. The unauthenticated request → 401
The client calls the MCP server with no token. The server responds 401 Unauthorized with a WWW-Authenticate header pointing at its Protected Resource Metadata, and, per RFC 6750 §3, SHOULD include the scope the operation requires:
That scope hint is what keeps agents from requesting the world on first contact. Clients MUST treat it as authoritative for the current operation.
2. Protected Resource Metadata discovery (RFC 9728)
The client fetches the metadata document. MCP servers MUST implement this; clients MUST use it for authorization server discovery. The real RFC 9728 field names, worth stating plainly because published examples get them wrong:
The document MUST include authorization_servers with at least one entry, and it's an array. scopes_supported is meant to be the minimal set needed for basic functionality, not a catalogue of every scope you support; extra permissions get requested incrementally later. Note also that clients MUST NOT assume any set relationship between the scopes named in a WWW-Authenticate challenge and scopes_supported. The challenge is authoritative for the current operation either way.
Clients use the resource_metadata URL from the WWW-Authenticate header when it's present, and MUST otherwise fall back to constructing the well-known URIs directly. A server only has to implement one of the two mechanisms, so a client that skips the fallback will fail against conformant servers.
3. Authorization server metadata discovery
The client fetches the AS's own metadata. Authorization servers MUST provide at least one of OAuth 2.0 Authorization Server Metadata (RFC 8414) or OpenID Connect Discovery 1.0; clients MUST support both and try them in priority order. Three client obligations here that are easy to miss:
- The
issuerin the returned document MUST be identical to the issuer identifier used to build the well-known URL. If they differ, the client MUST NOT use the metadata. - The client MUST record that validated
issuervalue and bind it to the same per-request record holding the PKCE code verifier andstate. Step 6's protection is worthless if the expected issuer came from an unvalidated source. - The client MUST verify PKCE support before proceeding, MUST use
S256where technically capable, and MUST refuse to proceed ifcode_challenge_methods_supportedis absent from the metadata.
4. Client registration
The agent needs a client_id, and it has never met this server before. Three mechanisms, plus a last-resort fallback. Clients that support all of them SHOULD use this priority order:
- Pre-registration, if the client already has credentials for this AS.
- Client ID Metadata Documents (CIMD): the default for the common case of no prior relationship. Check for
client_id_metadata_document_supported: truein the AS metadata. - Dynamic Client Registration (RFC 7591): deprecated, fallback only. If you do use it you MUST specify an appropriate
application_type("native"for desktop, mobile, CLI and localhost apps;"web"for remote browser-based ones). Omitting it defaults to"web"under OIDC, which collides with native redirect URIs. - Prompt the user to paste credentials, if nothing else works.

One rule that applies to options 1 and 3: client credentials are bound to the authorization server that issued them. Clients MUST key persisted credentials by issuer, MUST NOT reuse them with a different AS, and MUST re-register when the AS changes.
Under CIMD, the client hosts a JSON document at an HTTPS URL and uses that URL as its client_id:
The authorization server detects the URL-formatted client_id, fetches the document, and validates that the document's own client_id matches the URL exactly and that the requested redirect_uri appears in redirect_uris. That exact-match check is what prevents client impersonation.
The spec doesn't give a rationale for the deprecation, but it does name one concrete advantage: CIMD client IDs are portable across authorization servers, "since they are self-hosted HTTPS URLs resolved by the authorization server on demand. No re-registration is needed when the authorization server changes." The commonly cited operational argument is that DCR obliges every authorization server to expose a public write endpoint that mints and persists a registration record, which scales badly when one popular AI client meets thousands of unfamiliar servers. CIMD inverts that to fetch-and-cache.
5. Authorization request with PKCE and resource
The client generates PKCE parameters and opens a browser:
PKCE is mandatory for all clients under OAuth 2.1, including confidential ones. The resource parameter is the part people miss: RFC 8707 requires it in both the authorization request and the token request, it MUST be the MCP server's canonical URI, and clients MUST send it whether or not the authorization server supports it.
Canonical URI means an absolute URI with no fragment: https://mcp.example.com/mcp, https://mcp.example.com, https://mcp.example.com:8443 and https://mcp.example.com/server/mcp are all valid; mcp.example.com (no scheme) and https://mcp.example.com#fragment are not. Clients SHOULD give the most specific URI they can. The canonical form uses lowercase scheme and host, but implementations SHOULD accept uppercase for robustness. Prefer no trailing slash, and note that this choice has to match the aud claim your server later checks, byte for byte.
6. Issuer validation on the callback (RFC 9207)
The user consents and the AS redirects back with a code. Authorization servers SHOULD include an iss parameter; where one is present, the client MUST validate it against the issuer it recorded in step 3 before sending that code anywhere:
Order matters: form-decode the iss value out of the application/x-www-form-urlencoded response first, then compare it with no further normalization. Clients MUST NOT apply scheme or host case folding, default-port elision, trailing-slash, or percent-encoding normalization before comparing. This defends against mix-up attacks, where a malicious AS tricks a client into redeeming a code at the wrong token endpoint. It applies to error responses too: on mismatch, the client must not even display the error_description.
7. Token exchange, then the authenticated call
The client redeems the code with code_verifier and resource, gets an access token, and attaches it to every subsequent request:
Authorization MUST be included in every HTTP request. This has been the rule since 2025-06-18, where it was phrased "even if they are part of the same logical session". 2026-07-28 removed protocol-level sessions entirely, so the caveat is simply moot now. Tokens MUST NOT appear in query strings.

Token validation and audience binding
This is the part that matters most and is covered least. On receiving a token, the MCP server MUST validate it per OAuth 2.1 §5.2 and MUST confirm the token was issued specifically for itself as the intended audience. The spec then adds three requirements:
"MCP clients MUST NOT send tokens to the MCP server other than ones issued by the MCP server's authorization server. MCP servers MUST only accept tokens that are valid for use with their own resources. MCP servers MUST NOT accept or transit any other tokens."
The attack this closes is what the spec calls access token privilege restriction. Without an audience check, an agent holding a valid token for Server A can present it to Server B; if B accepts any well-formed token from the same issuer, B acts on A's authority. Related and separately forbidden: a server that forwards a token it received upstream ("token passthrough") launders the audience entirely. As the spec puts it, "the MCP server MUST NOT pass through the token it received from the MCP client."

The spec's distinct confused deputy section covers a different case worth knowing about: an MCP proxy using a static client ID MUST obtain user consent for each dynamically registered client before forwarding to a third-party authorization server, or a stolen authorization code can ride the proxy's identity.
If your authorization server issues JWT access tokens (RFC 9068), validation looks like this, using jose and no vendor SDK:
Two things to note. requiredClaims is doing real work: jose validates exp when the claim is present but does not demand it, so a token minted without an expiry would otherwise sail through. And aud matching is exact with no normalization: if your AS mints aud: "https://mcp.example.com/" and CANONICAL_URI omits the trailing slash, every request 401s.
If your authorization server issues opaque tokens instead, none of the above applies. MCP requires validation per OAuth 2.1 §5.2, which RFC 7662 token introspection satisfies equally. Either way, everything past validation is yours to build: which scopes gate which tools.
Error handling
Step-up authorization: when an agent needs more permission mid-task
An agent authorized for files:read tries to write. The server SHOULD respond:
The client then:
- Parses the error and the required scopes from the header.
- Computes the union of scopes it previously requested and the scopes in this challenge. This step is not optional bookkeeping. Servers are only required to name the scopes for the current operation, so a client that re-authorizes with just
files:writesilently losesfiles:read. Scope accumulation is a client-side responsibility. - Re-authorizes with that union.
- Retries the original request, no more than a few times, then treats it as a permanent failure.
Two things are asked of server authors, at different strengths. Servers SHOULD emit all scopes an operation needs in a single challenge rather than drip-feeding them one round-trip at a time. Incremental challenges force repeated authorization trips for one operation and wreck the user experience. And servers MUST account for scope hierarchies, where a broader scope implies narrower ones, when deciding whether a token is sufficient.
Clients acting for a user SHOULD attempt step-up. Clients acting on their own behalf via client_credentials MAY attempt it or just abort, which points at a real open question below.
Which spec revision are you building against?
If a guide you're reading mentions DCR without mentioning CIMD, it predates November 2025. If it presents step-up authorization as brand new, it's describing 2025-11-25.
What the spec leaves to you
The authorization spec is deliberately narrow. Four things it does not answer:
- Who is the resource owner when no user is present? The spec accommodates
client_credentialsclients but says little about them. An agent running on a schedule with no human to consent is a genuinely unsolved delegation problem, not a solved one. - Which tools may this identity call? A valid token with
files:writedoesn't tell you whether this agent should be writing to this path. That's authorization policy, and it's entirely yours. - How do you run an authorization server? "Beyond the scope of this specification", which is accurate and also the largest piece of work in the list. CIMD validation alone means fetching remote documents (with SSRF defenses), caching them on HTTP headers, and exact-matching redirect URIs.
- How do you keep up? Four revisions in sixteen months, each moving normative requirements.
That last one is why most teams delegate the authorization server rather than build one. AuthKit implements the current spec (CIMD, Resource Indicators, PRM, issuer validation), so your MCP server keeps only the part that has to live in your code: validating the token and deciding what its bearer may do.
FAQ
Is MCP authorization required?No. It's OPTIONAL in the spec. But if your server is remote and touches real data, you need it. And if you implement it over HTTP, you SHOULD conform to the spec rather than invent something.
Do local or STDIO MCP servers need OAuth?No. STDIO implementations SHOULD NOT follow the authorization spec; they take credentials from the environment.
Is Dynamic Client Registration still the right way to register clients?No. As of 2026-07-28, DCR is deprecated in favor of Client ID Metadata Documents and retained only for backwards compatibility with authorization servers that don't support CIMD yet. New implementations should use CIMD.
What goes in the resource parameter, and why?The canonical HTTPS URI of the MCP server you intend to call. It tells the authorization server which audience to bind the token to, so the token can't be replayed against a different server. Required in both the authorization and token request, and required whether or not your AS supports it.
How does an MCP server verify a token was issued for it?Validate the aud claim against the server's own canonical URI. Reject anything else, and never forward a received token upstream.
Does the MCP server issue its own tokens?It shouldn't. It's a resource server. Earlier spec revisions pushed servers toward minting tokens bound to a third-party session; that guidance is gone.
Why is PKCE mandatory?OAuth 2.1 requires it for all clients, including confidential ones, because it binds the authorization code to the client that requested it and defeats code interception on the redirect.
What happens when an agent needs more permissions mid-task?The server returns 403 with error="insufficient_scope" and the required scopes. The client re-authorizes with the union of old and new scopes, then retries.
How do refresh tokens work for MCP clients?Clients that want them SHOULD include refresh_token in their grant_types metadata and MAY add offline_access to the scope parameter when the AS advertises it, but MUST NOT assume refresh tokens will be issued, since the AS retains discretion. MCP servers SHOULD NOT advertise offline_access in WWW-Authenticate or scopes_supported; refresh is a client concern, not a resource requirement.
Is MCP authorization the same as MCP authentication?No. Authentication is who's calling; authorization is what they may do. The spec covers the token handshake and leaves per-tool policy to you.
Which MCP spec revision should I build against?2026-07-28. Confirm any guide you follow names its revision; several widely-cited explainers still describe 2025-03-26.