Manufact

MCP Authentication: A Complete Guide
Back to Blog
Engineeringmcpsecurityauthenticationoauthproduction

MCP Authentication: A Complete Guide

Pietro Zullo

Pietro Zullo

Co-founder

March 3, 2026·22 min read
Share:

When you connect an AI agent to your company's internal tools, the first question every security team asks is: "How is this authenticated?"

It's the right question. These agents aren't just reading data, they're calling APIs, querying databases, triggering workflows. They can search customer records, generate reports, update tickets. The blast radius of a misconfigured auth flow is real.

As a developer deploying MCP in production, authentication is consistently the topic that takes the longest to get right. Not because the spec is bad, it's actually well-designed, but because MCP introduces a twist that traditional OAuth wasn't built for: the client is dynamic.

In a normal web app, your OAuth client is always the same application. Your frontend, your mobile app, it's registered once and that's it. In MCP, the client changes constantly. A user might connect from ChatGPT in the morning, Claude in the afternoon, and VS Code the next day. Each is a different OAuth client, and your Auth Server has never seen most of them before.

This guide walks through how MCP solves this, layer by layer.

Two Layers, Two Problems

The first thing that clicks when you start working with MCP auth is that there are actually two separate authentication problems to solve:

Loading diagram...

Layer 1 handles the connection between the AI client (ChatGPT, Claude, VS Code) and the MCP Server. This is fully defined by the MCP specification and builds on OAuth 2.1, familiar territory for most developers, with some MCP-specific twists.

Layer 2 is the connection between the MCP Server and your backend APIs. This is where it gets interesting, because the MCP spec explicitly says you cannot just forward the client's token upstream. You need a separate authentication strategy for each backend service.

Let's start with Layer 1, since everything else builds on top of it.

Layer 1: How the Client Authenticates to the MCP Server

Starting Simple: API Keys

The fastest way to get an MCP connection authenticated is to hardcode a token in the client's configuration file:

{ "mcpServers": { "my-server": { "url": "https://mcp.example.com", "headers": { "Authorization": "Bearer sk-my-api-key-123" } } } }

The client sends this token on every request, the server validates it, and you're done.

Loading diagram...

This works great for prototyping, internal tools, and CI/CD pipelines, anywhere the client is trusted and you don't need to know who is making requests. But it falls apart quickly in production. The key sits in a plaintext config file. It can get committed to git, shared on Slack, or leaked in a screenshot. There's no per-user identity, no token expiry, and no way for browser-based clients like ChatGPT or Claude to use it, they expect an OAuth login flow, not a pasted API key.

Moving to OAuth 2.1

For production, the MCP spec defines a full OAuth 2.1 flow. If you've implemented OAuth before, most of this will feel familiar. The MCP-specific parts are the discovery mechanism and how client registration works.

Here's the full flow, step by step.

It Starts With a 401

The client makes its first request to the MCP Server without any credentials. The server responds with a 401 Unauthorized and a WWW-Authenticate header that essentially says: "You need to authenticate. Here's where to find my configuration."

HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource", scope="tools:read tools:execute"

This is the entry point. The client now knows two things: where to find the server's authentication metadata, and what scopes it needs to request.

Discovery: The Server's Business Card

The client fetches the Protected Resource Metadata (PRM), a JSON document that the MCP Server hosts at a well-known URL. Think of it as the server announcing: "Here's who I am, here's who can issue tokens for me, and here's what permissions I support."

{ "resource": "https://mcp.example.com", "authorization_servers": ["https://auth.example.com"], "scopes_supported": [ "tools:read", "tools:execute", "data:search", "data:detail" ], "bearer_methods_supported": ["header"] }

The resource field becomes the aud (audience) claim in tokens, it's how the server knows a token was actually meant for it. The authorization_servers field tells the client where to go to get those tokens. And scopes_supported advertises what permissions exist.

This discovery flow is what makes MCP interoperable. A client can connect to any MCP Server it has never seen before, fetch this document, and know exactly how to authenticate. No out-of-band configuration, no manual setup. It's defined in RFC 9728.

The client then fetches the Authorization Server metadata to learn about endpoints, supported registration methods, and capabilities.

The Dynamic Client Problem

Here's where MCP diverges from traditional OAuth.

In a normal OAuth setup, you register your application once on the Auth Server, you get a client_id, configure your redirect URIs, and you're done. But in MCP, who is "the client"? It's not a single application. It's whatever AI tool the user happens to be using. ChatGPT today, Claude tomorrow, a custom agent built with LangChain next week.

You can't pre-register all of them. And you don't want to, the whole point of MCP is interoperability. Any client should be able to connect to any server.

The spec provides three strategies for solving this, each designed for different scenarios:

Strategy 1: Pre-Registered Client, the simple case. If you know which clients will connect (internal deployment, controlled partner), you register them ahead of time. The client already has its client_id and goes straight to the OAuth flow.

Loading diagram...

This works with any standard OAuth 2.1 IDP and is the most secure option, the Auth Server knows exactly which clients are allowed. The trade-off is that every new client needs manual registration, so it doesn't scale to open ecosystems.

Strategy 2: Client ID Metadata Documents (CIMD), the spec's recommended default for unknown clients. Instead of registering on the Auth Server, the client hosts a JSON metadata document at an HTTPS URL. That URL is the client_id.

Loading diagram...

When the Auth Server sees a URL-formatted client_id, it fetches the document, validates that the client_id inside matches the URL, checks the redirect_uri, and proceeds. No registration step, no orphaned records on the Auth Server.

The metadata document is simple:

{ "client_id": "https://app.example.com/oauth/client-metadata.json", "client_name": "My MCP Client", "redirect_uris": ["http://127.0.0.1:3000/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none" }

The catch? No major IDP supports CIMD natively today, not Cognito, not Entra ID, not WSO2. It's a new spec (draft-ietf-oauth-client-id-metadata-document), and adoption is still early. This is one of the main reasons MCP Auth Gateways exist, they bridge this gap.

Strategy 3: Dynamic Client Registration (DCR), the backward-compatible fallback. The client registers itself at runtime by calling the Auth Server's /register endpoint.

Loading diagram...

DCR has wider IDP support than CIMD, ChatGPT uses it today. But it has a well-known problem: every session can create a new registration, and Auth Servers end up accumulating tens of thousands of orphaned client records. This is exactly why CIMD was created as a replacement.

MethodPrior relationship?IDP requirementScales to unknown clients?
Pre-registeredYesStandard OAuth 2.1No
CIMDNoCIMD support (or gateway)Yes
DCRNoRegistration endpoint (RFC 7591)Yes (but creates orphaned clients)

The OAuth Flow Itself

Once the client has a client_id (however it got one), the standard OAuth 2.1 Authorization Code flow kicks in:

Loading diagram...

Two MCP-specific requirements stand out here:

PKCE is mandatory. PKCE (Proof Key for Code Exchange) ensures the client that started the authorization flow is the same one that receives the callback. This matters a lot for MCP because clients often run on localhost, VS Code, terminal tools, local agents. Without PKCE, an attacker on the same machine could intercept the redirect URI and steal the authorization code. The client proves continuity by sending a code_verifier that matches the code_challenge it committed to earlier.

The resource parameter is mandatory. The resource parameter (RFC 8707) tells the Auth Server which MCP Server this token is for. The Auth Server stamps the token with aud=mcp.example.com, so it can't be used at a different MCP Server. This audience binding is a critical security boundary, without it, a token leaked from one MCP Server could be replayed at another.

After Authentication: Token Validation

From this point, the client includes the access token in every MCP request. The server validates four things on each request:

  1. The token was issued by the configured Auth Server
  2. The token's audience matches this MCP Server
  3. The token scopes are sufficient for the requested operation
  4. The token hasn't expired

If a tool requires scopes the user hasn't granted, the server responds with 403 insufficient_scope. The client can then perform step-up authorization, requesting additional scopes without restarting the entire flow. This enables a gradual consent experience: users grant basic access first, and the system asks for elevated permissions only when needed.

Authorization: What Can This User Do?

Authentication tells you who the user is. But knowing their identity isn't enough, you also need to decide what they're allowed to do. This is where scopes and roles come in.

Scopes: The Broad Strokes

Scopes are coarse-grained permissions baked into the OAuth flow. The MCP Server declares what scopes it supports in its Protected Resource Metadata, and the client requests them during authorization:

tools:read , discover and list tools tools:execute , call tools data:search , search data data:detail , view full records admin:manage , admin operations

A user granted tools:read tools:execute data:search can discover tools and search data, but trying to access full records triggers a 403 insufficient_scope, prompting the client to re-authorize with broader permissions.

Roles: The Fine-Grained Control

Roles come from OIDC token claims and allow much finer control. Even if two users have identical scopes, their roles can mean very different things:

RoleVisible ToolsData DepthRate Limit
analystSearch, SummarySummary only100 req/hr
managerAll analyst tools + ReportsFull records500 req/hr
adminAll tools + User mgmtFull + audit logsUnlimited

The IDP manages who has what role. The MCP Server reads the role claim from the token and decides what it means, which tools to show, how much data to return, and how many requests to allow.

Loading diagram...

This enforcement happens at two points. First, at tool discovery (tools/list): the MCP Server filters the tool list based on the user's role, so an analyst never even sees admin tools, the agent doesn't know they exist. Second, at tool execution (tools/call): even if a tool is visible, the server checks authorization before running it.

Loading diagram...

Why This Matters More for AI

One thing that catches developers off guard: when an AI agent replaces the human clicking through a UI, the implicit rate limiting of that UI disappears. A human might view 10 customer records in an hour. An agent can request 10,000. RBAC combined with rate limiting, thoughtful tool design (search-by-name instead of list-all), and scope-based access control is the primary defense against data scraping through MCP.

Where Does the MCP Server Live?

Before we talk about Layer 2, there's an architectural question that determines whether you even need it.

Colocated: Embedded in Your Backend

If your MCP Server lives inside your backend, same process, same deployment, it can call internal services directly. No network hop, no second auth layer.

Loading diagram...

This eliminates Layer 2 entirely, no token exchange, no federation. It's the lowest-latency option and works well for single-service backends. But it has real limitations: the MCP Server is coupled to your backend's language, framework, and deployment cycle. You can't scale or version it independently. And as soon as you need tools from multiple services, you're back to making network calls, and back to needing Layer 2.

Standalone: MCP Server as Its Own Service

In most production deployments, the MCP Server runs as a standalone service that aggregates tools from multiple backends:

Loading diagram...

This gives you aggregation (one MCP Server, many backends), independent deployment, centralized controls (rate limiting, audit logging, RBAC all in one place), and clean team ownership boundaries. The trade-off is that you now need Layer 2, the MCP Server must authenticate to each upstream API separately.

Layer 2: How the MCP Server Authenticates to Your APIs

Here's the part that surprises most developers: you cannot forward the client's token to your backend APIs.

Why Token Passthrough Is Forbidden

The MCP spec explicitly calls this out as an anti-pattern. The reasoning is sound:

The client's token has audience=mcp-server.example.com. Your upstream API expects audience=api.example.com. If the API is properly configured, it rejects the token. If it's not properly configured and accepts it anyway, you've created a security hole, a token meant for one service is being accepted by another.

Beyond the audience mismatch, passthrough creates a confused deputy problem (the API can't tell if the request is from a legitimate MCP Server or an attacker replaying a stolen token), breaks your audit trail (the API doesn't know which client or user originated the request), and makes rate limiting at the MCP layer meaningless (if the token works directly on the API, why go through the MCP Server at all?).

So what do you do instead? There are three valid patterns.

Pattern A: Token Exchange

The MCP Server takes the client's token and exchanges it at the Auth Server for a new token scoped to the upstream API. The user's identity is preserved, the new token still says "this is user X", but the audience changes.

Loading diagram...

This is the ideal pattern when the same Auth Server (or federated Auth Servers) protect both the MCP Server and the upstream API. Per-user identity flows end-to-end, and every service sees a properly-scoped token.

Pattern B: SSO Federation

When the upstream API uses a different Auth Server, but there's a trust relationship between the two:

Loading diagram...

This handles cross-domain scenarios, for example, when some services are on Cognito and others on Entra ID, with federation between them.

Pattern C: Service Account

The simplest approach. The MCP Server authenticates to upstream APIs using its own credentials. The user's token is used only at the MCP layer for identity and RBAC, it never touches the upstream API.

Loading diagram...

The MCP Server may have broad access upstream, but enforces per-user restrictions through tool visibility, data filtering, and rate limiting. This is often the right starting point, it's simple, requires no token exchange support from your IDPs, and all authorization logic lives in one place.

The MCP Auth Gateway

At this point, you might be thinking: "My IDP doesn't support CIMD. It doesn't support DCR either. And I have three different IDPs across my services. How do I make this work?"

This is where an MCP Auth Gateway comes in. It sits between MCP clients and your real IDPs, presenting a fully spec-compliant authorization server to clients while handling all the heterogeneity behind the scenes.

Loading diagram...

The gateway registers a single pre-registered client_id on each real IDP, a one-time setup. It then exposes full MCP auth spec endpoints to clients: CIMD support, DCR, Protected Resource Metadata, PKCE, resource parameters. When a client authenticates, the gateway assigns it a virtual client_id, handles the real OAuth flow with the underlying IDP, and re-issues a token with the correct audience.

Loading diagram...

Beyond spec compliance, the gateway gives you token isolation (the upstream IDP's token never reaches the client), IDP decoupling (swap out an IDP without touching any MCP Server or client), unified redirect URIs (one callback URL instead of one per client per IDP), and a centralized audit trail across all IDPs.

What's Coming: Enterprise-Managed Authorization

There's a draft MCP extension that addresses one of the most natural questions teams ask when deploying MCP: "Why can't we just use our existing SSO?"

It's a fair question. Today, when Claude connects to an MCP Server via OAuth, your IDP sees "user logged into the MCP Server." It doesn't know Claude is involved. It has no visibility into which AI agent is accessing which tools. From the IDP's perspective, it's invisible.

The Enterprise-Managed Authorization extension (sometimes called Cross-App Access or XAA) changes this by putting the IDP in the middle of every agent-to-server connection:

Loading diagram...

The key insight: no consent screen. Steps 2-3 happen automatically. The IDP checks policy — "Can Claude access this MCP Server for this user?" — and either issues the token or denies the request. Silently, for every MCP Server the admin has approved. You control which clients access which servers, per-user or per-group, with scope restrictions and full audit trails. Revocation happens in one place.

This is still a draft extension, Okta's XAA Phase 1 is in early access, and no MCP client ships it yet. But it's clearly where the ecosystem is heading.

The Reality of IDP Support Today

Not all IDPs support all MCP auth strategies, and this fragmentation is one of the biggest practical challenges when deploying MCP in production:

FeatureAWS CognitoMicrosoft Entra IDOktaWorkOS
OAuth 2.1 + PKCEYes (S256 only)YesYesYes
OIDC (identity claims)YesYesYesYes
Metadata endpointYes (both)Yes (OIDC)YesYes
Resource parameter (RFC 8707)Yes (native)No (uses scope instead)YesYes
Token exchange (RFC 8693)Custom onlyYesYesNo
DCR (RFC 7591)Custom (via Lambda)NoYesYes
CIMDNoNoNoYes
XAA / Enterprise AuthNoNoEarly accessNo

A few things stand out. WorkOS is currently the only IDP with native CIMD support, which makes it the closest to full MCP spec compliance out of the box. Entra ID notably lacks both DCR and the resource parameter, making it one of the harder IDPs to use with MCP directly. And no traditional IDP supports CIMD natively (except WorkOS, which was built with MCP in mind).

This fragmentation is the main reason MCP Auth Gateways exist, they bridge the gap between what the spec requires and what most IDPs actually provide today.

Client support is similarly uneven:

ClientPre-registeredDCRCIMD
ChatGPTYesYesNo
ClaudeYesYesNo
VS Code (Copilot)YesYesYes
CursorYesYesNo

The ecosystem is converging toward CIMD as the default, but we're not there yet. Having an auth layer that normalizes these differences is what makes production deployments feasible today.

Choosing Your Path

MCP authentication isn't one-size-fits-all. The right approach depends on where you are:

Prototyping or internal tools? Start with API keys in config. Get something working, validate the use case, worry about OAuth later.

Production with known clients? Pre-registered OAuth. Works with any IDP, no spec extensions needed, maximum control.

Production with unknown or third-party clients? You need CIMD (via a gateway) or DCR as a fallback. The gateway handles spec compliance regardless of your IDP's limitations.

Connecting to multiple backend services? Deploy the MCP Server as a standalone service. Use token exchange if your IDPs support it, service accounts as a simpler starting point. The standalone architecture gives you aggregation, independent scaling, and centralized controls.

Need visibility and control over which agents access what? Keep an eye on the Enterprise-Managed Authorization extension. It brings SSO-like control over which agents access which tools, with full audit trails.

The MCP auth spec is still evolving. But the foundation is solid: standard OAuth 2.1, standard RFCs, standard security patterns. The MCP-specific additions, metadata discovery, CIMD, resource binding, solve real problems that come from having dynamic clients connecting to distributed tools. These patterns are production-tested, and they'll continue to work as the spec matures.

References

MCP Specification

OAuth & Identity RFCs

Further Reading

Get Started

What will you build with MCP?

Start building AI agents with MCP servers today. Connect to any tool, automate any workflow, and deploy in minutes.