AIARTICLE

MCP gets official authorization specification with OAuth 2.1

The Model Context Protocol specification formalizes how tool servers must authenticate clients, changing what used to be improvised in integrations with Claude, Copilot, and custom agents.

MCP gets official authorization specification with OAuth 2.1
Image: Alan Andrade

The Model Context Protocol (MCP) published, in the 2025-06-18 revision of its specification, an Authorization section that stops treating authentication as an implementation detail and now requires a formal flow based on OAuth 2.1. For those who already expose tools via an MCP server to Claude, Copilot, or custom agents, this isn't just documentation: it defines the handshake format, the required headers, and, most importantly, what a server can no longer accept from a client.

Why This Needed to Exist

Until now, authorization in MCP servers was open terrain. Each implementation solved it its own way: a fixed token in an environment variable, a simple API key, or nothing at all. That worked for prototyping, but broke down as soon as a server needed to accept multiple clients, delegate access to third-party resources, or simply avoid trusting blindly whoever connected.

The official specification solves this by relying on four already mature standards from the OAuth ecosystem: OAuth 2.1 as the basis of the flow, RFC 8414 (authorization server metadata), RFC 7591 (dynamic client registration), and RFC 9728 (protected resource metadata). The real novelty isn't using OAuth, it's MCP stating exactly which subset of these specs is mandatory and how the pieces fit together between client, MCP server, and authorization server.

One point that's already causing confusion: the requirement applies to HTTP transport. Servers that run via STDIO (the common case for local tools called by a host like Claude Desktop) should, per the spec, not follow this flow, and should instead pull credentials directly from the environment. If your MCP server runs locally, the OAuth 2.1 issue simply doesn't apply; the requirement is for those exposing a remote endpoint.

How the Discovery Flow Works

The central piece is the separation of roles: the MCP server acts as an OAuth 2.1 resource server, the client acts as an OAuth client, and the one issuing tokens is the authorization server, which can be hosted alongside the MCP server or on an entirely different domain.

The MCP server needs to expose a Protected Resource Metadata document (RFC 9728) with the authorization_servers field, indicating where the client should go to fetch the token. In practice, this looks like this when the client tries to access without a token:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.exemplo.com/.well-known/oauth-protected-resource"

The client needs to know how to parse that header (the spec makes this mandatory, not optional) and from there proceed to the Authorization Server Metadata (RFC 8414) of the indicated authorization server, which provides the authorization and token endpoints and, if supported, dynamic client registration (RFC 7591). This last point solves a real practical problem: without dynamic registration, every MCP client would need a fixed client ID manually registered with each authorization server it wanted to access, which is unworkable when the goal is to connect to MCP servers unknown in advance.

The resource Parameter and the Confused Deputy Problem

The most technically important part of the spec is the requirement for the resource parameter (RFC 8707) in every authorization and token request. It explicitly identifies which MCP server will use that token:

GET /authorize?client_id=abc123&response_type=code
 &resource=https%3A%2F%2Fmcp.exemplo.com
 &code_challenge=...&code_challenge_method=S256 HTTP/1.1

Without this binding, a token issued to access service A could, in theory, be reused against service B, if B doesn't validate the audience. This is exactly the scenario the spec calls confused deputy: an MCP server acting as a proxy to third-party APIs can be tricked into passing along a stolen token, and the downstream API ends up trusting it as if it had legitimately come from the MCP server.

The rule that closes this gap is direct: "MCP servers MUST NOT pass through the token it received from the MCP client" when making calls to upstream APIs. If your MCP server today simply forwards the Bearer token received from the client to a third-party API (a common and convenient pattern), that's no longer acceptable. The server needs to act as its own OAuth client with the upstream, using a separate token.

What Breaks for Those Who Implemented Before the Spec

Those who already had an MCP server in production with custom authentication will feel three points of friction:

  • Token without audience. If the server accepts any valid Bearer token without checking who it was issued for, this is now explicitly prohibited. The spec requires validating the audience claim and rejecting tokens that weren't issued specifically for that server.
  • Absence of discovery. Servers that relied on the client already knowing the auth endpoint (via manual config, shared environment variable) no longer have this as a recommended path; the expectation is to respond with 401 and WWW-Authenticate pointing to the Protected Resource Metadata.
  • Lack of PKCE. The spec makes PKCE mandatory in the authorization code flow, something simplified implementations (static client secret, no code challenge) didn't have.

It's worth reinforcing: for servers that operate only via STDIO, none of this applies directly, and it's tempting to think you can ignore the whole spec just because the transport is local. The risk appears when that same tool code is later exposed via HTTP to multiple users, a scenario where the security gap becomes real.

How to Bring a Server Into Compliance Today

A reasonable path to migrate an existing HTTP MCP server:

  1. Publish the Protected Resource Metadata document at /.well-known/oauth-protected-resource, listing the valid authorization server(s).
  2. Respond with 401 and WWW-Authenticate: Bearer resource_metadata="..." for any request without a token or with an invalid token, instead of a generic error.
  3. In the token handshake, validate the audience claim against the server's own canonical URI (no trailing slash, scheme and host in lowercase, following RFC 8707 guidance).
  4. If the server calls upstream APIs on behalf of the user, exchange it for its own token via a separate OAuth flow, never resending the token received from the MCP client.
  5. Make sure the application's client sends the resource parameter in every authorization request and token request, even if the authorization server in use doesn't validate this yet.

None of these points requires rewriting the logic of the tools themselves (the "core" of the MCP server), but all of them touch the HTTP transport layer, which is exactly where most homegrown implementations cut corners.

What Remains Open

The spec makes clear that implementing the authorization server itself is out of scope for MCP: it's up to each team to decide whether to host their own (using something like an existing OIDC provider) or point to a separate authorization server. This means that, in practice, teams will keep choosing among varied solutions (Auth0, Keycloak, a homegrown authorization server), and real interoperability will depend on how strictly each of these pieces follows RFC 8414 and RFC 9728. The spec settles "who does what" on paper; the mess of incomplete implementations will still show up in MCP servers published without a security review.

Translated from the Brazilian Portuguese original · Read the original

View profile →