← Back to blog
·By MCPCore Teammcpauthenticationoauthapi-keyssecurity

MCP Server Authentication: OAuth 2.1 vs API Keys

The MCP specification formally requires OAuth 2.1 for authorization over HTTP transports, but API keys remain the simpler, more common choice for most servers. Here is how to pick between them.

If you're deciding how to authenticate calls to your MCP server, the two realistic options are a static API key or a full OAuth 2.1 flow. They solve the same basic problem (who is allowed to call this server) in very different ways, and the right choice depends more on who your users are than on which one is "more secure."

What the Spec Actually Requires

Authorization is optional in MCP: a server can legitimately run with no authentication at all if it only exposes non-sensitive, public data. But when a server does implement authorization over an HTTP-based transport, the specification says it should conform to its authorization framework, and that framework is built on OAuth 2.1. As of the 2026-07-28 specification, MCP servers that support OAuth are formally OAuth 2.1 resource servers and must implement OAuth 2.0 Protected Resource Metadata (RFC 9728) so clients can discover the right authorization server automatically.

That is a spec-level statement about what "OAuth support" means if you choose to offer it. It does not mean every MCP server needs OAuth. API keys are a separate, simpler mechanism that the spec does not prohibit and that most servers use in practice.

API Keys: Simple and Predictable

An API key is a static secret sent in the Authorization: Bearer header on every request.

POST /mcp HTTP/1.1 Authorization: Bearer sk-mcpcore-AbCdEfGhIjKlMnOpQr... Content-Type: application/json

Validating it server-side is a straightforward comparison against a stored value:

app.post("/mcp", (req, res, next) => { const authHeader = req.headers["authorization"] ?? ""; const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null; if (!token || token !== process.env.MCP_API_KEY) { return res.status(401).json({error: "Unauthorized"}); } next(); });

Strengths: trivial to implement, easy to rotate by issuing a new key and revoking the old one, and works with every MCP client without any additional configuration on the client side.

Limitations: every holder of the key has identical access. There is no built-in concept of per-user permissions, and if the key leaks (client config files are a common leak point), the finder has full access until you notice and revoke it.

API keys are the right fit when your MCP server has a single class of caller: your own tools, a specific partner integration, or a small team that can share one credential responsibly.

OAuth 2.1: Delegated, Per-User Authorization

OAuth hands authentication off to an external identity provider. The client completes an authorization flow, receives a token scoped to a specific user, and your server validates that token on every request using the provider's JWKS endpoint.

import jwksClient from "jwks-rsa"; import jwt from "jsonwebtoken"; const client = jwksClient({jwksUri: process.env.JWKS_URI}); async function validateToken(token) { const decoded = jwt.decode(token, {complete: true}); const key = await client.getSigningKey(decoded.header.kid); return jwt.verify(token, key.getPublicKey(), { audience: process.env.OAUTH_AUDIENCE, issuer: process.env.OAUTH_ISSUER, }); }

The 2026-07-28 spec tightened several parts of this flow specifically: authorization servers must return the iss parameter (RFC 9207) so clients can confirm the token came from the server they expected, which closes off a class of OAuth mix-up attacks, and client credentials are now bound to the issuing server so a token obtained from one authorization server cannot be replayed against another.

Strengths: each user gets their own scoped identity, so a tool that queries "the current user's records" can actually enforce that boundary. Revoking one user's access doesn't affect anyone else.

Limitations: meaningfully more work to implement correctly. You need an identity provider, a discovery endpoint, and token validation logic, and getting any of it wrong (skipping iss validation, for example) reintroduces exactly the kind of vulnerability the spec update was written to close.

OAuth is the right fit for multi-tenant servers where different users should see different data, or where you want to plug into an identity system you already run.

A Third Option: Neither

Public mode, no authentication at all, is appropriate for servers that only expose data you would be comfortable publishing on the open internet. It should be a deliberate choice, not a default you forgot to change.

Choosing in Practice

ScenarioRecommendation
Internal tool, one team, trusted networkAPI key
Public demo, non-sensitive data onlyPublic, no auth
SaaS product exposing per-user dataOAuth 2.1
Quick integration with a specific partnerAPI key
Enterprise deployment with an existing identity providerOAuth 2.1

Most MCP servers in production today use API keys, not because OAuth is unnecessary but because most servers have one class of caller rather than many distinct end users. Reach for OAuth when the thing your tool exposes is genuinely per-user, not by default.

If you want both options available without implementing either yourself, MCPCore supports four authentication modes (Public, API Key, OAuth 2.0, and Bearer Token) per server, switchable without touching your tool code. See MCP server security for details on how each mode works.


Authorization requirements are based on the current MCP specification. Check modelcontextprotocol.io for the authoritative and most current authorization spec.