MCP authorization is the part of the Model Context Protocol spec that says how a client proves it may call a remote MCP server. In practice it is OAuth: the MCP server acts as an OAuth resource server, a separate (or co-hosted) authorization server issues tokens, and the client finds that authorization server on its own by following a 401 response. No API key gets pasted anywhere. The user signs in once in a browser, and the client attaches a bearer token to every request after that.
There have been four versions of the rules since March 2025. Guides written before November 2025 describe Dynamic Client Registration as the default. The current revision, dated 2026-07-28, deprecates it in favour of Client ID Metadata Documents. This guide follows that revision, checked on October 5, 2026. We use real responses from our own server, mcp.dialmcp.com, which you can reproduce with curl.
What MCP authorization covers, and what it does not
The spec opens with a sentence people skip: "Authorization is OPTIONAL for MCP implementations." When a server does support it, the transport decides which rules apply:
- HTTP transports (Streamable HTTP, the standard transport for remote servers) SHOULD conform to the authorization spec.
- stdio servers SHOULD NOT follow it. They should "retrieve credentials from the environment" instead, which in practice means an API key in an environment variable set in the client's config file.
- Other transports must follow the security practices of their own protocol.
So if you are writing a local server that your own laptop launches, OAuth is the wrong tool. If the server lives at a URL that other people's agents will connect to, it is the expected one. Our remote vs local MCP servers explainer covers that split in more depth.
There are three parties, and keeping them apart solves most confusion:
| Role | Who plays it | What it does |
|---|---|---|
| MCP client | Claude, ChatGPT, Codex, VS Code, Cursor, your own agent | Acts as the OAuth client. Discovers the authorization server, runs the browser sign-in with PKCE, stores and sends tokens. |
| MCP server | The remote endpoint, for example https://mcp.dialmcp.com/mcp | Acts as the OAuth resource server. Publishes protected resource metadata, validates that each token was issued for it, answers 401 or 403. |
| Authorization server | Your own OAuth service, or an identity provider such as Auth0, Okta, Entra ID, WorkOS or Keycloak | Signs the user in, records consent, issues access and refresh tokens. It "may be hosted with the resource server or a separate entity." |
The first version of the spec (2025-03-26) made the MCP server act as the authorization server too. That proved awkward for anyone with an existing identity provider, and since 2025-06-18 the MCP server is only a resource server. It can still run its own authorization server on the same host, as DialMCP does, but the two jobs are now defined separately.
The MCP OAuth flow, traced against a live server
Here is the whole sequence, using the responses mcp.dialmcp.com returned on October 5, 2026. Every step is something a client does automatically when you paste a server URL into it.
Step 1: call the server with no token and get a 401
$ curl -si -X POST https://mcp.dialmcp.com/mcp \
-H 'content-type: application/json' -d '{}'
HTTP/2 401
www-authenticate: Bearer realm="OAuth", error="invalid_token",
error_description="Missing or invalid access token",
resource_metadata="https://mcp.dialmcp.com/.well-known/oauth-protected-resource/mcp"
The status code matters as much as the header. Claude's connector docs say outright that "Claude does not honor a WWW-Authenticate header on a 200 response." The resource_metadata parameter tells the client where to look next. The spec says servers SHOULD also include a scope parameter naming the scopes this request needs.
Since 2025-11-25 the header is optional. If it is missing, the client falls back to well-known URLs, trying the path-aware form first (/.well-known/oauth-protected-resource/mcp for a server at /mcp) and then the root form. Sending the header anyway saves a round trip and removes guesswork, so do it.
Step 2: read the protected resource metadata
$ curl -s https://mcp.dialmcp.com/.well-known/oauth-protected-resource/mcp
{"resource":"https://mcp.dialmcp.com/mcp",
"authorization_servers":["https://mcp.dialmcp.com"],
"bearer_methods_supported":["header"],
"resource_name":"dialmcp"}
This document is defined by RFC 9728. The two fields that matter are resource, which must be identical to the URL the client connected to, and authorization_servers, which the MCP spec requires to list at least one issuer (RFC 9728 itself makes it optional). RFC 9728 is strict about the first one: if resource does not match, the client "MUST NOT" use the data. Claude adds that it matches against the URL "exactly as the user enters it," path included, and that when several authorization servers are listed it uses the first one and never falls back to the others.
Step 3: discover the authorization server
$ curl -s https://mcp.dialmcp.com/.well-known/oauth-authorization-server
{"issuer":"https://mcp.dialmcp.com",
"authorization_endpoint":"https://mcp.dialmcp.com/authorize",
"token_endpoint":"https://mcp.dialmcp.com/token",
"registration_endpoint":"https://mcp.dialmcp.com/register",
"grant_types_supported":["authorization_code","refresh_token"],
"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","none"],
"code_challenge_methods_supported":["plain","S256"], ...}
An authorization server must publish metadata in at least one of two formats: RFC 8414 OAuth metadata or OpenID Connect Discovery. Clients must try both, in a fixed order. For an issuer with no path, that order is /.well-known/oauth-authorization-server and then /.well-known/openid-configuration. For an issuer with a path, such as https://auth.example.com/tenant1, it is:
https://auth.example.com/.well-known/oauth-authorization-server/tenant1https://auth.example.com/.well-known/openid-configuration/tenant1https://auth.example.com/tenant1/.well-known/openid-configuration
Two checks happen here. First, the 2026-07-28 text adds an issuer check: the issuer in the document must be identical to the one used to build the URL, or the client must not use it. Second, the client looks for code_challenge_methods_supported. If that field is absent, the spec says the server does not support PKCE and the client "MUST refuse to proceed." OpenAI repeats the rule for ChatGPT, where servers whose metadata omits the field are unsupported. A missing field here is an easy way for a server to work in one client and fail in another.
Step 4: register the client
The client now needs a client_id that this authorization server recognises. There are three ways to get one, covered in the next section. DialMCP's metadata has a registration_endpoint and does not advertise Client ID Metadata Document support. Clients therefore register with it through Dynamic Client Registration, which is the path Claude documents as its fallback.
Step 5: browser sign-in with PKCE and a resource indicator
The client opens the browser at the authorization endpoint with a PKCE code_challenge (method S256), a state value, its redirect URI, and a resource parameter set to the MCP server's URL. The user signs in and approves. On DialMCP, that sign-in includes verifying a US or Canadian mobile number by SMS, because the verified number becomes the caller ID on every call. The authorization server redirects back with a code.
If the authorization response includes an iss parameter, the 2026-07-28 revision requires the client to compare it with the recorded issuer by exact string match before redeeming the code. That defends against mix-up attacks, covered below.
Step 6: exchange the code and call the server
The client posts the code, the PKCE code_verifier, and the same resource value to the token endpoint. It gets an access token back, and usually a refresh token. From then on every request carries Authorization: Bearer <token>. The spec says authorization "MUST be included in every HTTP request from client to server," and tokens must never go in the query string.
To test this sequence against your own server, connect to it from the MCP Inspector, which runs the same OAuth sign-in a real client would.
Client registration: CIMD, DCR, or a pre-registered client
Registration is where the spec has moved most. Under OAuth, an authorization server normally knows its clients in advance. MCP breaks that assumption, because any of thousands of clients may connect to any of thousands of servers with no prior relationship. Three mechanisms exist, and the 2025-11-25 revision set a priority order that 2026-07-28 keeps:
| Order | Mechanism | How it works | Status in 2026-07-28 |
|---|---|---|---|
| 1 | Pre-registered client | The client already holds a client_id (and maybe a secret) for this authorization server, typically entered by the user or an admin. | Clients SHOULD offer an option for it. |
| 2 | Client ID Metadata Document (CIMD) | The client_id is an HTTPS URL the client controls, such as https://chatgpt.com/oauth/client.json. The authorization server fetches it to learn the client's name and redirect URIs. | SHOULD, for both clients and authorization servers. Used when metadata has client_id_metadata_document_supported. |
| 3 | Dynamic Client Registration (DCR, RFC 7591) | The client POSTs its details to registration_endpoint and receives a fresh client_id. | Deprecated "in favor of Client ID Metadata Documents" but kept for backwards compatibility. Removal is no earlier than the first spec revision released on or after 2027-07-28. |
| 4 | Ask the user | No automatic option works, so the client prompts for credentials. | Last resort. |
Why the shift? DCR makes every authorization server accept registrations from strangers. That creates piles of throwaway clients and, combined with proxy servers that use a static client ID, it enables the confused deputy attack described below. With CIMD, the client's identity is tied to a domain it controls, and the same client_id can work across authorization servers.
Client support follows the spec, with a lag. Claude supports both CIMD and DCR out of the box. It picks CIMD only when the metadata advertises "client_id_metadata_document_supported": true and lists none under token_endpoint_auth_methods_supported. Otherwise it falls back to DCR. OpenAI's docs say ChatGPT handles CIMD, DCR, and predefined OAuth clients. VS Code added CIMD in version 1.106 and prefers it over DCR when the server supports it. If you run a server today, supporting both is the practical answer. Keep DCR for older clients and add CIMD for the new ones.
The 2026-07-28 revision adds two registration rules worth knowing. DCR clients must now send an appropriate application_type, with native apps using "native". And credentials are bound to the authorization server that issued them, so clients must store them keyed by issuer, never reuse them with a different authorization server, and register again if the server changes.
Tokens: audience, resource indicators, and refresh
Getting a token is half of MCP server authentication. The other half is making sure a token cannot be replayed somewhere it was not meant for.
- Resource indicators (RFC 8707). Clients must send the
resourceparameter in both the authorization request and the token request, "regardless of whether authorization servers support it." The value is the server's canonical URI:https://mcp.example.com/mcpis valid. A baremcp.example.comis not, and neither is a URI with a fragment. Prefer no trailing slash. - Audience validation. MCP servers "MUST validate that access tokens were issued specifically for them as the intended audience." A token minted for some other API must be rejected, even if the same identity provider signed it.
- No token passthrough. If your MCP server calls an upstream API, it must not forward the token it received from the client. It gets its own token for the upstream service. The spec is blunt: servers "MUST NOT accept or transit any other tokens."
- Short lifetimes and rotation. Authorization servers SHOULD issue short-lived access tokens, and for public clients they MUST rotate refresh tokens.
- Refresh tokens are not guaranteed. The 2026-07-28 text adds a section on them. Clients may request
offline_accesswhen the authorization server lists it inscopes_supported, and they must not assume a refresh token will arrive. MCP servers should not putoffline_accessin their own scope challenges.
An expired or invalid token gets a 401, which sends a good client back to refresh or sign in again. Claude Code, for example, refreshes and retries once when a 401 arrives and it has a stored token.
Scopes and step-up authorization
For the first sign-in, the client SHOULD request the scopes named in the 401's scope parameter. If there is none, it requests everything in the resource metadata's scopes_supported, or omits scope entirely when that field is missing too.
Later, a tool might need more than the user first granted. The server then answers 403 with an insufficient_scope challenge:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="files:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
error_description="File write permission required for this operation"
The client re-runs authorization for the larger scope set and retries, "no more than a few times." The 2026-07-28 text moves scope bookkeeping to the client: it computes the union of what it asked for before and what the new challenge names. In 2025-11-25 the server was recommended to repeat previously granted scopes in the challenge. Either way, the design goal is the same. Ask for a small scope up front and elevate only when a tool needs it, because a stolen broad token does more damage.
How MCP authorization changed, revision by revision
If you read an older tutorial, this table tells you what has moved since it was written.
| Revision | What changed for authorization |
|---|---|
| 2024-11-05 | No authorization section. |
| 2025-03-26 | First OAuth 2.1 framework. The MCP server is the authorization server (or proxies one). Metadata found by dropping the URL path. DCR SHOULD. PKCE required. Default /authorize, /token, /register if there is no metadata. |
| 2025-06-18 | MCP server becomes a resource server. RFC 9728 protected resource metadata MUST. WWW-Authenticate on 401 MUST. RFC 8707 resource indicators, audience validation, and the token passthrough ban arrive. Security best practices page added. |
| 2025-11-25 | CIMD added as the recommended registration method. DCR drops to MAY. OpenID Connect Discovery accepted alongside RFC 8414. WWW-Authenticate becomes optional with a well-known fallback. Clients must verify PKCE support and use S256. Scope selection and 403 step-up added. |
| 2026-07-28 (current) | DCR deprecated in favour of CIMD. Authorization servers SHOULD send iss (RFC 9207) and clients MUST validate it when present. DCR clients must send application_type. Credentials bound to the issuing authorization server. The spec text also adds issuer equality checks on metadata, a refresh token section, and client-side scope union. The protocol itself became stateless, so "session hijacking" guidance became "state handle hijacking." |
One naming note. "MCP OAuth 2.1" refers to the OAuth 2.1 specification, which is still an IETF Internet-Draft and not an RFC. The MCP spec pins draft 13. Client ID Metadata Documents are also an IETF draft.
Implementing MCP server authentication: a checklist
For a remote server, the minimum that today's clients expect:
- Return 401 with
WWW-Authenticate: Bearer resource_metadata="…"for any request without a valid token. Includescopeif you use scopes. - Serve protected resource metadata at the path-aware well-known URL, and the root one too if you can. Make
resourcematch the exact URL users paste. - Point
authorization_serversat an issuer whose RFC 8414 or OIDC metadata listscode_challenge_methods_supportedwithS256. - Support CIMD (advertise it, and accept the
nonetoken auth method), and keep a DCR endpoint for older clients. - Allow the redirect URIs your target clients use. Claude's hosted surfaces use
https://claude.ai/api/mcp/auth_callback. Claude Code uses a localhost loopback on a random port, and Anthropic says your server must accepthttp://localhost/callbackandhttp://127.0.0.1/callback"with the port component ignored." VS Code listshttp://127.0.0.1:33418andhttps://vscode.dev/redirect. ChatGPT useshttps://chatgpt.com/connector_platform_oauth_redirectwhen your authorization server supports RFC 9207iss, and a per-connectorhttps://chatgpt.com/connector/oauth/{callback_id}URL otherwise. - Validate every token's audience. Never forward client tokens upstream.
- Answer quickly. Claude allows 10 seconds for discovery, registration and token calls, and your token endpoint must accept
application/x-www-form-urlencodedbodies.
A note from building our own server: the OAuth provider library we built on for Cloudflare Workers handled the authorize, token and registration endpoints, but it did not serve RFC 9728 metadata. Clients saw a bare 401 with nowhere to go. The fix was small. We added a handler for both well-known paths and a wrapper that appends resource_metadata to any 401 challenge that lacks it. If a client fails right after the first 401, check those two things before anything else. Our guide to building an MCP server shows where auth sits in a remote server, and our OAuth docs describe the user side of the same flow.
Common MCP authentication failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Client says "needs authentication" but never opens a browser | 401 lacks resource_metadata and no well-known metadata is served | Serve protected resource metadata and add the header |
| Works in one client, ignored by ChatGPT | code_challenge_methods_supported missing, or lacks S256 | Advertise ["S256"] in authorization server metadata |
| Metadata found, then discovery aborts | resource differs from the connected URL (trailing slash, missing /mcp), or issuer differs from the issuer listed in authorization_servers | Make both values byte-for-byte identical |
| Redirect error after sign-in | The client's callback URI is not allowed | Allow the documented callback URLs, and loopback ports for local clients |
Entra ID error AADSTS9010010 | The MCP server URL is not registered as an Application ID URI | Register it, per Anthropic's connector docs |
| Signed in, but every tool call returns 401 | Token audience does not match the server, or the token is being sent somewhere other than the header | Check the resource value and audience validation; send Authorization: Bearer |
For per-client steps to confirm, re-authenticate, and read logs, see our client setup docs.
Security pitfalls the spec calls out
The MCP security best practices page names specific attacks. The ones that touch authorization:
- Confused deputy. An MCP server that proxies to a third-party authorization server with one static client ID can be tricked into issuing a code to an attacker's dynamically registered client, using a consent cookie left from a real user. Proxies MUST get per-client user consent.
- Token passthrough. Accepting tokens not issued for your server bypasses its controls and breaks audit trails.
- Mix-up attacks. A malicious authorization server tricks the client into sending it a code meant for an honest one. The best practices page notes that "PKCE alone does not prevent this attack." Validating
issdoes, provided the honest authorization server sends it. - SSRF in discovery. A hostile server can put internal addresses into
resource_metadataorauthorization_servers, so clients should not fetch them blindly. - Authorization URL validation. Clients must only open
http://andhttps://authorization URLs, and must not open them through shell commands. - State handle hijacking. Possessing a server-issued handle, such as a cart ID, is not authentication. Bind handles to the user in the verified token.
If you put a gateway in front of several servers, these rules apply to it too. See MCP gateway vs a hosted remote MCP for where auth lives in each setup.
What authorization does not do
OAuth answers "who is this, and did they consent?" It does not answer "should this action happen?" That second question belongs to the server.
DialMCP makes a useful example because its tools act in the real world: place_call rings an actual phone. A valid token proves that a person verified a mobile number and approved the connection. It does not bypass anything. Every call still goes out from that person's own verified number, with server-enforced AI disclosure, destination-local calling hours, rate limits, and a permanent opt-out list. Those checks run on the server for every request, whichever client sent it. The safety page lists them.
The same split applies to any server with write tools. Use OAuth to establish identity and consent, keep scopes small, and enforce the rules that matter inside the server itself.
Further reading
- Remote MCP servers explained
- Add an MCP server to Claude
- ChatGPT MCP setup
- DialMCP hosted endpoint reference
- MCP specification 2026-07-28: Authorization
- MCP specification 2026-07-28: changelog
- MCP security best practices
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- Anthropic: connector authentication
- OpenAI: authentication for MCP servers
See the flow from the user's side. Add https://mcp.dialmcp.com/mcp to Claude, ChatGPT or Codex, sign in, verify your own mobile number, and your agent can place real phone calls with a transcript and a structured outcome. Free during launch.
FAQ
What is MCP authorization?
It is the part of the Model Context Protocol spec that defines how clients get access to remote MCP servers. The MCP server acts as an OAuth resource server, an authorization server issues tokens, and the client finds that authorization server by following a 401 response to protected resource metadata.
Does MCP use OAuth 2.1?
Yes. The spec requires authorization servers to implement OAuth 2.1, which is still an IETF Internet-Draft rather than an RFC. MCP also builds on RFC 9728 protected resource metadata, RFC 8414 or OpenID Connect discovery, RFC 8707 resource indicators, and PKCE.
Do stdio MCP servers need OAuth?
No. The spec says stdio implementations should not follow the authorization spec and should read credentials from the environment instead, usually an API key set in the client's config file. OAuth is for servers reached over HTTP.
Is Dynamic Client Registration still supported in MCP?
It still works, but the 2026-07-28 revision deprecates it in favour of Client ID Metadata Documents and keeps it for backwards compatibility. It cannot be removed before the first spec revision released on or after 2027-07-28. Supporting both is the practical choice for servers today.
What is the difference between MCP authentication and authorization?
Authentication establishes who the user is, usually by signing in at the authorization server. Authorization decides what the client may do, expressed as an access token with an audience and scopes. The MCP spec section is called authorization, but the OAuth flow it describes covers both.
Why does my MCP server work in Claude but not in ChatGPT?
One likely cause is authorization server metadata without code_challenge_methods_supported, or without S256 in it. OpenAI says servers whose metadata omits that field are unsupported. Also check that your protected resource metadata resource value exactly matches the URL users paste.