If you followed a Model Context Protocol authentication tutorial written before mid-2025, there is a fair chance you built the wrong thing. The early guidance had your MCP server mint its own tokens and act as its own authorization server. The June 2025 revision of the spec deleted that model, labeled the change breaking, and a lot of the write-ups still circulating never caught up.

The corrected model is easier to reason about, and it is the one every well-built API already uses. Your MCP server is not an identity provider. It is a resource server: it validates tokens that a separate authorization server issued, and it rejects everything it cannot verify. If you have secured an API with OAuth before, you already know most of what follows. If you have not, the failure modes waiting for you are the same ones that have caught teams for a decade.

MCP authorization sequence: the client calls the server with no token, gets a 401 with a WWW-Authenticate header, fetches protected resource metadata to find the authorization server, runs the authorization code flow with PKCE and a resource indicator, receives an audience-bound access token, and calls the server with a Bearer token
The client discovers where to authenticate from a 401, runs OAuth 2.1 with PKCE and a resource indicator, and comes back with a token scoped to this one server.

The server validates, the authorization server issues

MCP servers expose tools and data to AI clients over HTTP, and once that endpoint is on the public internet it needs what any API needs: a way to tell an allowed caller from everyone else. Since the 2025-06-18 revision, MCP's answer is OAuth 2.1 with the roles kept strictly apart.

Your MCP server plays the resource server. It never runs a login screen, never issues a token, never stores a password. When a request lands it checks one thing: is there a valid access token, issued by an authorization server I trust, whose audience is me? If yes, run the tool call. If no, reject it.

The authorization server is a separate role. It can run on the same host or be a managed identity provider you point at, but it owns everything MCP deliberately leaves out: authenticating the user, showing consent, minting and rotating tokens. The spec says its implementation is out of scope, which is the polite way of telling you not to build one.

One boundary before the details: this is the model for HTTP transports. If your server only speaks over stdio, the spec says to take credentials from the environment instead, and authorization is optional overall. Everything below is for the server you put on a URL.

How a client finds its way in

A client showing up cold does not know which authorization server to use, and the discovery handshake is where most of the new machinery lives.

The client calls your server without a token. Your server answers 401 Unauthorized with a WWW-Authenticate header pointing at your protected resource metadata. That document, served at /.well-known/oauth-protected-resource under RFC 9728, lists the authorization server or servers you accept. The client reads it, then fetches the authorization server's own metadata (RFC 8414) to learn where to send the user to log in and where to exchange the code for a token. If the client has never registered with that server, it can register itself on the spot through Dynamic Client Registration (RFC 7591) and get a client_id without anyone filing a ticket. The spec recommends supporting that rather than requiring it, since a client cannot know every server it might meet in advance.

Notice what is missing: appending /authorize and /token to your server's URL. The old revision let clients guess endpoints that way. The current one removed the fallback and made this explicit metadata the only path, because a guessed endpoint is an endpoint an attacker can stand in for.

Two rules that keep a token from wandering

Two requirements do the heavy security lifting, and the MCP spec makes both mandatory.

PKCE is mandatory. Most MCP clients are public clients with no server-side secret, so the authorization code that returns through the browser has to be useless to anyone who intercepts it. PKCE makes the client prove, at token exchange, that it is the same party that started the flow. This is one of the tightenings OAuth 2.1 made non-negotiable, and the OAuth 2.1 vs OIDC piece walks through why.

Resource indicators bind the token to you, and this is the rule most older MCP write-ups skip. Under RFC 8707, the client must send a resource parameter naming your server's canonical URL, on both the authorization request and the token request. The authorization server stamps that value into the token's audience, and your server then refuses any token whose audience is not you. Without it, a token minted for one MCP server can be replayed against another. With it, a stolen token is worthless anywhere but the single server it was scoped to.

A token scoped to one MCP server: the client sends resource equals the server URL on the authorize and token requests, the authorization server stamps that as the token audience, and only the server whose identifier matches accepts it while any other server rejects it with a 401
The resource indicator makes the token's audience a specific server. Present it anywhere else and validation fails.

The mistakes the spec tells you to avoid

The spec ships a security best-practices document, and it reads like a catalogue of ways teams have already been burned. The ones worth keeping in front of you:

Never pass a token through. If your server wraps a downstream API, forwarding the caller's token onward is tempting and forbidden. A token issued for your server has your server as its audience; sending it upstream breaks that boundary, sidesteps the upstream's own checks, and shreds your audit trail. When your server needs to call something else, it becomes an OAuth client there and gets its own token.

Validate the audience before you do anything else. A token that is perfectly valid and correctly signed but was minted for a different service is not a token for you. Confirm your server's identifier is in the audience claim and reject it if not. Resource indicators put the right value there; audience validation is what makes putting it there matter.

Treat discovery URLs as untrusted. A malicious server can hand a client metadata that points the authorization or token endpoint at an internal address, your cloud metadata service included. Clients have to enforce HTTPS, refuse private and link-local ranges, and mind the DNS-rebinding window.

Get consent per client if you proxy. A server that fronts a third-party authorization server with a single static client_id while letting clients register dynamically can be walked into a confused-deputy attack: a leftover consent cookie lets an attacker skip the consent screen and capture a code. If you build that kind of proxy, take explicit user consent for each dynamically registered client before you forward anything.

A session is not authentication. Verify every request. Do not let a session id stand in for a validated token, keep session ids non-deterministic, and bind them to the user so one cannot be lifted and replayed.

Build it in this order

Standing up an MCP server over HTTP is a small amount of work, and the order keeps you from painting yourself into a corner:

  1. Pick an authorization server. Use a managed identity provider or an OAuth 2.1 server you already run. Do not write one.
  2. Publish protected resource metadata at /.well-known/oauth-protected-resource listing that authorization server, and return a WWW-Authenticate header on every 401 pointing to it.
  3. Validate every inbound token: signature, issuer, expiry, and that your server's canonical URL is in the audience. Answer 401 for an invalid token, 403 for insufficient scope, 400 for a malformed request.
  4. Require the resource indicator so tokens are audience-bound to you, and require PKCE on the client.
  5. Never forward a caller's token upstream. Get a separate token for any downstream call.
  6. Serve everything over HTTPS, keep scopes least-privilege, and check authorization on every request rather than trusting a session.

Questions

Frequently asked questions

Does an MCP server need authentication?

Only if it is exposed over HTTP, and even then the spec makes authorization optional. A server that speaks over stdio takes its credentials from the environment and skips this entirely. But the moment an MCP server is reachable over a network, it needs the same protection any API does, and OAuth 2.1 is the model the spec defines for it.

Can an MCP server issue its own tokens?

Not under the current spec. Since the 2025-06-18 revision, the MCP server is strictly an OAuth 2.1 resource server: it validates tokens a separate authorization server issued and never mints them itself. The earlier revision did let the server act as its own authorization server, which is why older tutorials still describe it that way, but that model was removed in a breaking change.

How does an MCP client find the authorization server?

It calls the server with no token and reads the 401 Unauthorized response. Under RFC 9728, that response carries a WWW-Authenticate header pointing to the server's protected resource metadata, a document that lists the authorization servers the MCP server accepts. The client fetches it, then reads the authorization server's own metadata (RFC 8414) to find where to log the user in and exchange the code.

What is a resource indicator, and why does MCP require it?

A resource indicator (RFC 8707) is a resource parameter the client sends on both the authorize and token requests, naming the MCP server's canonical URL. It tells the authorization server to stamp that URL into the token's audience, so the token is valid only at that one server. Without it, a token minted for one MCP server could be replayed against another; with it, a stolen token is useless anywhere else.

Can an MCP server forward a user's token to another API?

No. Token passthrough is explicitly forbidden. A token issued for your server has your server as its audience, so sending it upstream breaks that boundary, sidesteps the upstream's own checks, and corrupts the audit trail. When your MCP server needs to call another API, it acts as an OAuth client there and gets its own separate token.

Where this leaves you

None of this is new cryptography or a novel protocol. It is the resource-server discipline that has guarded APIs for years, pointed at a new kind of endpoint, which is exactly why the June 2025 revision moved MCP onto it. An MCP server is just the latest place an old rule applies: make every token mean one specific thing, check it on every request, and never let it travel somewhere it was not addressed to.

That token-boundary discipline is the backend and API security work we do, and it is the same seam we wrote about in OAuth 2.1 vs OIDC. If you are putting an MCP server in front of real tools and real data, the auth is not the exciting part, but it is the part that decides whether the thing is safe to expose at all.