Submit

MCP Authentication: How OAuth 2.1 Secures MCP Servers

MCP authentication secures MCP servers with OAuth 2.1: how the authorization flow works step by step, how to implement it, and the mistakes that leak tokens.

Written by AiAgentsListing Team

•7 min read
MCP Authentication: How OAuth 2.1 Secures MCP Servers

Most MCP servers still run on a bare API key

Astrix Security analyzed more than 5,200 open source MCP server implementations and found that 88% require some form of credential to run. Only 8.5% of them use OAuth. The rest lean on static API keys or personal access tokens sitting in an environment variable, the same pattern web APIs mostly abandoned a decade ago. The Model Context Protocol's own specification asks for something better, and getting MCP authentication right is now the difference between a server anyone can call and one that knows exactly which agent, on whose behalf, is making each request.

MCP authentication is the OAuth 2.1 based system the Model Context Protocol specification defines for HTTP-hosted servers: the MCP server acts only as an OAuth resource server, a separate authorization server issues short-lived bearer tokens through a PKCE-protected flow, and the client sends that token with every request. Local, stdio-based servers are explicitly excluded and use environment credentials instead.

The Model Context Protocol authorization specification's Standards Compliance list, showing OAuth 2.1 and the seven RFCs and OpenID Connect specs it builds on, including RFC 9728 Protected Resource Metadata and RFC 8707 Resource Indicators.

Why MCP needs authentication now

Early MCP servers ran on the same machine as their client, talking over stdio, so there was nothing to authenticate. Once servers moved onto the network over Streamable HTTP, an MCP client became something that could request another user's data on a remote box, and the protocol needed a real delegated-authorization model instead of a shared secret. The specification answered that by making the MCP server an OAuth 2.1 resource server rather than its own identity provider, so it validates tokens issued elsewhere instead of managing logins itself.

The stakes are not hypothetical. In June 2025, researchers documented an incident nicknamed "NeighborJack," in which hundreds of MCP servers were found bound to 0.0.0.0 by default, reachable by any device on the same network with no authentication at all. In January 2026, three CVEs landed in Anthropic's own reference Git MCP server, the template many developers copy first: a path traversal bug, a file deletion bug, and a chain with a filesystem server that produced remote code execution.

Prerequisites

  • An MCP server running over Streamable HTTP or SSE. The spec says stdio implementations "SHOULD NOT" follow this authorization flow at all, since a local process already runs under the user's own permissions and should pull credentials from the environment instead.
  • Access to an OAuth 2.1 authorization server. That can be a self-hosted option like Keycloak, which MCP's own tutorial uses for its walkthrough, or a hosted one such as WorkOS AuthKit or Descope's Agentic Identity Hub.
  • One of the official MCP SDKs (TypeScript, Python or C#), so the 401 response, the metadata documents and the bearer-token middleware don't have to be written from scratch.
  • A registered redirect URI, and if you want clients to onboard without manual setup, an authorization server that supports Dynamic Client Registration (RFC 7591) or the newer OAuth Client ID Metadata Documents.

MCP clients such as Claude Code already run this exact flow when they connect to a remote, authenticated MCP server, so most of the work described below sits on the server side.

Step 1: Return a 401 with Protected Resource Metadata

When an unauthenticated request hits your MCP endpoint, the server responds with 401 Unauthorized and points the client at a Protected Resource Metadata (PRM) document, defined in RFC 9728, through the WWW-Authenticate header:

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

The Model Context Protocol's official authorization tutorial walking through step 1 of the flow, the 401 response with a WWW-Authenticate header pointing the client at the Protected Resource Metadata document.

Step 2: Publish Protected Resource Metadata

The PRM document at that well-known path tells the client which authorization server issues tokens for this resource and which scopes exist:

{
"resource": "https://your-server.com/mcp",
"authorization_servers": ["https://auth.your-server.com"],
"scopes_supported": ["mcp:tools", "mcp:resources"]
}

The client then queries that authorization server's own metadata endpoint, using OpenID Connect Discovery or OAuth 2.0 Authorization Server Metadata (RFC 8414), to learn the actual authorize, token and registration endpoints. This is a hard requirement, not a suggestion: the specification's June 2025 revision removed the old fallback of guessing at /authorize, /token and /register paths in favor of mandatory RFC 9728 discovery.

Step 3: Register the client

Before the authorization code flow starts, the client needs a client_id. MCP's own tutorial uses Dynamic Client Registration for this, sending a request like the following to the authorization server's registration endpoint:

{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}

The July 28, 2026 spec revision changed one thing here worth knowing: registered credentials are now bound to the specific authorization server that issued them. If a resource migrates to a different authorization server, the client has to register again rather than reusing the old client ID.

Step 4: Run the OAuth 2.1 authorization code flow with PKCE

The client opens a browser to the authorization server's /authorize endpoint with a PKCE code challenge attached. The user logs in, grants the requested scopes, and the authorization server redirects back with a code the client exchanges for tokens:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "def502...",
"token_type": "Bearer",
"expires_in": 3600
}

PKCE is mandatory because most MCP clients, CLIs and desktop apps included, are public clients that cannot keep a secret. The specification's Resource Indicators requirement (RFC 8707) adds another constraint: the client must include a resource parameter naming the exact MCP server the token is meant for, in both the authorization request and the token request, so a token minted for one server cannot be replayed against another.

Descope's summary table of the MCP Authorization Specification's core requirements, listing OAuth 2.1, Dynamic Client Registration, Authorization Server Metadata, Resource Indicators and Protected Resource Metadata each marked MUST or SHOULD.

Step 5: Validate the token, including its audience, on every request

The client attaches the access token as a bearer token on every call:

GET /mcp HTTP/1.1
Host: your-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

The server, acting purely as a resource server, has to check more than whether the token is valid: it must confirm the token was actually issued for it, using the aud claim, per RFC 8707 Section 2. In MCP's official TypeScript example, the token verifier pulls the audience list out of the introspection response and rejects the request if none of the audiences match the server's own canonical URL, rather than falling back to trusting the caller.

Common mistakes

  • Token passthrough. Forwarding a bearer token to a downstream API without checking it was minted for your server is explicitly forbidden by MCP's security best practices document. It breaks the OAuth audience boundary and can let a token stolen from one service reach another.
  • Skipping audience validation. A server that accepts any token from the right issuer, regardless of the aud claim, will process a token that was never meant for it.
  • Folding the resource server and authorization server into one role. The June 2025 revision separated these on purpose. Undoing that reopens the confused deputy problem the spec describes: an MCP proxy with a static client ID and a lingering consent cookie can let an attacker skip the user's consent screen entirely.
  • Requesting scopes one at a time. The spec's scope-challenge guidance says a server should return every scope a request needs in a single 403 response. Challenging incrementally forces repeated authorization round trips for what should be one operation.
  • Shipping a static API key instead of any of this, which is still how the majority of public MCP servers authenticate according to Astrix Security's audit, and exactly the gap the NeighborJack incident exploited.

What's new (as of 30 September 2026)

  • The MCP specification's July 28, 2026 revision layered six requirements onto the OAuth 2.1 baseline: clients now validate the iss parameter on authorization responses against RFC 9207 to block mix-up attacks, clients declare an OpenID Connect application_type of web or native at registration, registered client credentials are bound to the issuing authorization server, refresh tokens are requested through the standard offline_access scope, scope step-up requests union previously granted scopes with newly required ones, and /.well-known/oauth-authorization-server discovery behavior is clarified.
  • That same revision promoted Enterprise-Managed Authorization to an official extension, built on the Identity Assertion JWT Authorization Grant. An enterprise identity provider issues a signed assertion under admin policy, and the MCP server's authorization server mints scoped tokens from it with no per-user consent screen, the pattern the industry calls Cross-App Access.
  • Astrix Security's analysis of over 5,200 open source MCP server implementations, reported on 17 March 2026, found 88% require credentials to function, 53% rely on static API keys or personal access tokens, and only 8.5% use OAuth.
  • Three CVEs landed in Anthropic's reference Git MCP server in January 2026: path traversal, file deletion, and remote code execution when chained with a filesystem server.
  • WorkOS shipped XMCP with built-in AuthKit configuration on 21 July 2025, letting a server declare its OAuth endpoints, issuer and default scopes directly in xmcp.config.ts instead of hand-writing the discovery and metadata routes.

Key takeaways

  • MCP authentication is optional in the specification but expected in practice for any server that is not purely local stdio.
  • The MCP server only ever validates tokens; a separate authorization server issues them. Merging the two roles reopens the confused deputy vulnerability the spec was rewritten to close.
  • Resource Indicators (RFC 8707) and audience validation exist specifically so a token minted for one MCP server cannot be replayed against another.
  • Just 8.5% of open source MCP servers use OAuth today, and 53% still run on static API keys, per Astrix Security's audit of 5,200+ implementations reported in March 2026.
  • The July 28, 2026 spec revision closes mix-up and credential-confusion gaps with issuer validation, client type declaration and authorization-server-bound credentials.

FAQ

Is authentication mandatory for MCP servers?

No. The specification calls authorization "OPTIONAL," but HTTP-based transports "SHOULD" implement it. Servers using stdio transport should skip this flow entirely and pull credentials from the environment instead, since a local process already runs under the user's own permissions.

What's the difference between MCP authentication and MCP authorization?

MCP's own specification is titled "Authorization" and focuses on OAuth 2.1 delegated access: proving a client is allowed to call a specific tool on behalf of a specific user. Authentication, confirming who that user or agent actually is, happens at the authorization server the MCP server delegates to, not inside the MCP server itself.

Can I use API keys instead of OAuth for an MCP server?

You can, and Astrix Security's research shows most public MCP servers still do. A static, long-lived key cannot be scoped per tool, cannot be revoked for one client without breaking every other client using it, and gives an attacker who steals it standing access until someone notices. The spec's OAuth 2.1 flow exists to replace that pattern with short-lived, audience-bound, per-client tokens instead.

Do I have to build my own authorization server?

No. Keycloak, open source and self-hosted, is what MCP's own tutorial uses for its walkthrough. Hosted options like WorkOS AuthKit or Descope's Agentic Identity Hub also implement the OAuth 2.1 roles the spec expects, including Protected Resource Metadata and Dynamic Client Registration, so the MCP server only has to validate tokens rather than issue them.

Developers comparing MCP servers that ship with authentication already built in can browse the current listings on the MCP servers hub.

Share:

Subscribe to our newsletter

One email a week. New agents, MCP servers and skills, and what is actually getting traction.

Read next

MCP Gateway: 8 Options Compared for 2026

MCP Gateway: 8 Options Compared for 2026

An MCP gateway sits between AI agents and MCP servers. Here are 8 options compared, from open source projects like Docker and Microsoft's to managed platforms like Composio and Kong.

9 min read