# OAuth 2.1 + PKCE Is the Only Right Way to Secure MCP

- Published: 2026-03-04
- Tag: SPEC
- URL: https://authplane.ai/blog/why-mcp-needs-oauth-21-pkce/
- Excerpt: The MCP authorization spec mandates OAuth 2.1 with mandatory PKCE for a reason. Here's exactly why every alternative falls apart.

---

## Why MCP Needs OAuth 2.1

When Anthropic published the MCP authorization specification, they made a clear choice: OAuth 2.1 with mandatory PKCE, not a simpler token scheme. This wasn't arbitrary. MCP servers are HTTP services that expose tools to AI agents. HTTP already has a mature, widely-adopted authorization framework. The question was never *whether* to use OAuth — it was *which version*, and with *what constraints*.

## The Problem with Older OAuth Flows

OAuth 2.0 (RFC 6749) was published in 2012. In the years since, a number of its flows have been deprecated because they create real security vulnerabilities:

- **Implicit grant**: Access tokens returned directly in the redirect URI fragment. Tokens end up in browser history, server logs, and HTTP referrer headers. Deprecated in OAuth 2.1.
- **Resource Owner Password Credentials**: User passes credentials directly to the client. Deprecated in OAuth 2.1.
- **Authorization code without PKCE**: Vulnerable to authorization code interception attacks — an attacker who intercepts the code can exchange it for a token.

## PKCE: Why S256 Only

PKCE (RFC 7636) adds a cryptographic binding between the authorization request and the token exchange. The client generates a random `code_verifier`, computes `code_challenge = BASE64URL(SHA256(code_verifier))`, and sends the challenge upfront. When exchanging the code, the server verifies `SHA256(verifier) == stored_challenge`.

The critical detail is **S256 only**. The RFC also defines a `plain` method where the verifier *is* the challenge — no hashing. This defeats the purpose entirely. AuthPlane rejects `plain` at the protocol level. Every authorization request without `code_challenge_method=S256` is rejected immediately.

## Single-Use Authorization Codes

Authorization codes are single-use and expire in 10 minutes. Consumption is atomic in the database — using `UPDATE ... WHERE consumed_at IS NULL RETURNING *`. If the row is already consumed, no rows are returned and the exchange fails. An attacker who intercepts a code after the legitimate client already exchanged it gets nothing.

## Refresh Token Rotation and Theft Detection

Access tokens expire in 15 minutes. Refresh tokens rotate on every use and belong to a *family*. If a consumed token is ever presented again, the entire family is revoked immediately. If an attacker steals a refresh token, the next legitimate refresh by the real client reveals the theft and terminates all sessions in the family.

## Summary

OAuth 2.1 with mandatory S256 PKCE provides what MCP agents need: protection against code interception, cryptographic binding between the authorization and exchange steps, and removal of deprecated flows. AuthPlane implements all of this from the RFC, not a library — no hidden defaults to hit.