AI & Agents

How to Add OAuth Authentication to Your MCP Server

If you're running a remote MCP server over HTTP, OAuth authentication isn't optional. The MCP specification (revised 2025-03-26) mandates OAuth 2.1 for any production server exposed over the network. This guide walks through the full setup: configuring Protected Resource Metadata, handling the authorization code flow with PKCE, validating tokens on your server, and enforcing scopes on individual tools.

Fast.io Editorial Team 10 min read
AI agent authentication and authorization workflow

Why MCP Servers Need OAuth

Most MCP tutorials skip authentication entirely. They show you how to run a server over stdio, where the transport itself provides isolation. Your MCP server runs as a subprocess, communicates through stdin/stdout, and the operating system handles access control. Remote MCP servers are different. When your server is reachable over HTTP, anyone with the URL can connect. Without authentication, every tool and resource your server exposes is open to the internet. The MCP specification addresses this directly. The 2025-03-26 revision added a full OAuth 2.1 authorization framework, built on top of established RFCs:

  • OAuth 2.1 (draft-ietf-oauth-v2-1-13) as the core protocol
  • RFC 9728 for Protected Resource Metadata, so clients can discover your auth requirements
  • RFC 7591 for Dynamic Client Registration (optional)
  • PKCE (RFC 7636) to prevent authorization code interception

OAuth is required for any production MCP server exposed over HTTP. Stdio-based servers should pull credentials from the environment instead.

How the MCP OAuth Flow Works

The OAuth flow for MCP has several steps. Understanding the sequence before writing code saves hours of debugging.

Step 1: Client sends an unauthenticated request. The MCP client connects to your server without a token.

Step 2: Server returns 401 with metadata pointer. Your server responds with HTTP 401 Unauthorized and a WWW-Authenticate header that points to your Protected Resource Metadata:

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

Step 3: Client discovers your authorization server. The client fetches your Protected Resource Metadata document, which lists your authorization server(s). It then fetches the authorization server's own metadata to learn its endpoints.

Step 4: Client registers (if needed). The client either uses a pre-registered client ID, presents a Client ID Metadata Document (an HTTPS URL pointing to its metadata), or registers dynamically via RFC 7591. Step 5: Authorization code exchange with PKCE. The client opens a browser, the user approves access, and the client exchanges the authorization code for an access token. PKCE (using the S256 challenge method) is mandatory for all clients.

Step 6: Client retries with token. The client sends the original MCP request with a Bearer token in the Authorization header. Your server validates the token and processes the request.

Server authorization hierarchy and access control flow

Setting Up Protected Resource Metadata

Protected Resource Metadata (RFC 9728) is how your MCP server tells clients where to authenticate. Without it, clients have no way to discover your authorization server. Your server needs to expose a JSON document at a well-known path. For a server running at https://mcp.example.com/api, the metadata lives at https://mcp.example.com/.well-known/oauth-protected-resource/api (path-based) or https://mcp.example.com/.well-known/oauth-protected-resource (root). Here's a minimal metadata document:

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

Key fields:

  • resource: The canonical URI of your MCP server. Clients use this as the resource parameter in their OAuth requests (per RFC 8707).
  • authorization_servers: One or more authorization servers that issue tokens for your server.
  • scopes_supported: The scopes your server recognizes. Keep this minimal. Start with read and execute permissions for tools and resources. Your server also needs to include the resource_metadata URL in WWW-Authenticate headers when returning 401 responses. This is the primary discovery mechanism.
Fast.io features

Give Your AI Agents Persistent Storage

Fast.io's official MCP server includes built-in OAuth 2.1 authentication, so your AI agents can securely access cloud storage without building auth from scratch.

Configuring Your Authorization Server

You don't need to build an authorization server from scratch. Use an existing provider like Auth0, Okta, Keycloak, or any OAuth 2.1-compliant service. Your authorization server needs to expose metadata at a well-known endpoint. For OAuth 2.0, that's /.well-known/oauth-authorization-server. For OpenID Connect, it's /.well-known/openid-configuration. MCP clients will check both. The metadata must include code_challenge_methods_supported with at least S256. If this field is missing, MCP clients will refuse to proceed. This is a hard requirement in the spec.

Configuring scopes

Define scopes that map to your MCP server's capabilities:

  • mcp:tools:read for listing available tools
  • mcp:tools:execute for calling tools
  • mcp:resources:read for accessing resources
  • mcp:prompts:read for listing prompts

You can get more granular. For example, mcp:tools:execute:weather could restrict access to a single tool. The MCP spec doesn't prescribe a specific scope naming convention, so pick one that fits your server.

Client registration The MCP spec supports three registration approaches, in priority order:

  1. Pre-registration: If you control both client and server, register the client ahead of time with a fixed client ID and redirect URIs.

Client ID Metadata Documents: The client provides an HTTPS URL as its client_id. Your authorization server fetches the metadata from that URL to learn about the client. This is the recommended approach for most MCP scenarios where clients and servers don't know each other in advance. 3. Dynamic Client Registration (RFC 7591): The client registers programmatically. Included for backward compatibility with earlier MCP spec versions. If your authorization server supports Client ID Metadata Documents, advertise it by including "client_id_metadata_document_supported": true in your authorization server metadata.

Validating Tokens on Your MCP Server

Once a client presents a token, your server is responsible for validating it. Treat every token as untrusted until verified.

What to validate

Signature: Verify the JWT signature against the authorization server's public keys (fetched from its JWKS endpoint). 2. Issuer (iss): Confirm the token was issued by your expected authorization server. 3. Audience (aud): The token must be intended for your server. Check that the audience claim matches your server's canonical URI. This prevents token reuse across services. 4. Expiration (exp): Reject expired tokens. 5.

Scopes: Extract the scopes from the token and check them against the requirements for the requested operation.

Example token validation middleware (Node.js)

import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json')
);

async function validateToken(req, res, next) {
  const auth = req.headers.authorization;
  if (!auth || !auth.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'missing_token' });
  }

try {
    const { payload } = await jwtVerify(
      auth.slice(7),
      JWKS,
      {
        issuer: 'https://auth.example.com',
        audience: 'https://mcp.example.com',
      }
    );
    req.tokenScopes = (payload.scope || '').split(' ');
    next();
  } catch (err) {
    return res.status(401).json({ error: 'invalid_token' });
  }
}

Scope enforcement

After validating the token, check that the client has the right scopes for the operation. A client with mcp:tools:read can list tools but shouldn't be able to execute them. ```javascript function requireScope(scope) { return (req, res, next) => { if (!req.tokenScopes.includes(scope)) { res.setHeader( 'WWW-Authenticate', Bearer error="insufficient_scope", scope="${scope}" ); return res.status(403).json({ error: 'insufficient_scope' }); } next(); }; }

// Apply to tool execution endpoint app.post('/tools/call', requireScope('mcp:tools:execute'), handleToolCall);


When a client lacks the required scope, return `403 Forbidden` with a `WWW-Authenticate` header specifying the needed scope. The client can then perform a step-up authorization to request additional permissions.
Token validation and audit logging for MCP server requests

Testing Your OAuth Setup

A broken OAuth flow shows up as a wall of 401 errors with no useful feedback. Test each piece individually before trying end-to-end.

Test Protected Resource Metadata ```bash

curl -s https://mcp.example.com/.well-known/oauth-protected-resource | jq . ```

Verify the response includes authorization_servers and scopes_supported. If this returns a 404, clients can't discover your auth requirements.

Test the 401 response

Send an unauthenticated request to your MCP endpoint and confirm the WWW-Authenticate header includes a resource_metadata URL:

curl -v https://mcp.example.com/mcp 2>&1 | grep WWW-Authenticate

Test token acquisition

Use your authorization server's test tooling (or curl with PKCE) to get a token. Then send it to your MCP server:

curl -H "Authorization: Bearer YOUR_TOKEN" https://mcp.example.com/mcp

Test scope enforcement

Get a token with limited scopes (e.g., only mcp:tools:read) and try to call a tool. You should get a 403 with insufficient_scope.

Common issues

  • Missing code_challenge_methods_supported: MCP clients will refuse to connect. Add S256 to your authorization server metadata.
  • Audience mismatch: The resource parameter in your Protected Resource Metadata must exactly match the audience your authorization server puts in tokens.
  • CORS headers: If your MCP client runs in a browser, your metadata and MCP endpoints need appropriate CORS headers.
  • Redirect URI mismatch: Client redirect URIs must exactly match what's registered. Even a trailing slash difference causes failures.

Production Considerations

Getting OAuth working in development is one thing. Running it in production introduces additional concerns.

Token lifetimes

Issue short-lived access tokens (5-15 minutes) to limit the impact of token theft. Use refresh tokens for long-running sessions. For public clients, rotate refresh tokens on every use.

Key rotation

Your authorization server's signing keys will rotate. Use the JWKS endpoint rather than hardcoding keys, and respect cache headers. Most JWT libraries handle this automatically with createRemoteJWKSet or equivalent.

Rate limiting

Add rate limiting to your metadata and MCP endpoints. Without it, a misconfigured client can overwhelm your server with repeated auth discovery requests.

Monitoring

Log authentication events: successful token validations, failures, scope escalation attempts, and expired tokens. This helps you catch misconfigured clients and potential attacks. Fast.io's MCP server handles all of this for you. The server implements the full OAuth 2.1 spec, including Protected Resource Metadata, PKCE, and token validation. If you need to connect AI agents to cloud storage, you can skip building your own auth layer and use Fast.io's official MCP integration with a free agent tier that includes 5,000 monthly credits. For teams building custom MCP servers, the patterns above give you a production-ready auth layer. Start with the metadata endpoint and 401 handling, add token validation, then layer on scope enforcement as your server grows.

Frequently Asked Questions

How do I add authentication to an MCP server?

Implement OAuth 2.1 by first exposing a Protected Resource Metadata document at a well-known path, then returning 401 responses with a WWW-Authenticate header that points to your metadata. Clients discover your authorization server from the metadata, run an OAuth authorization code flow with PKCE, and present the resulting Bearer token on subsequent requests. Your server validates the token's signature, issuer, audience, and scopes before processing each request.

Does MCP support OAuth?

Yes. The MCP specification added OAuth 2.1 support in the 2025-03-26 revision. OAuth is the standard authorization mechanism for any MCP server exposed over HTTP. The spec requires PKCE for all clients, mandates Protected Resource Metadata (RFC 9728) for auth discovery, and supports three client registration approaches: Client ID Metadata Documents, pre-registration, and Dynamic Client Registration.

How do I secure a remote MCP server?

Secure a remote MCP server by implementing OAuth 2.1 with PKCE, serving Protected Resource Metadata so clients can discover your auth requirements, validating JWT tokens on every request (checking signature, issuer, audience, and expiration), and enforcing scopes on individual tools and resources. Also use HTTPS for all endpoints, issue short-lived tokens, and monitor authentication failures.

What authentication does MCP use?

MCP uses OAuth 2.1 for HTTP-based transports. The protocol requires PKCE (Proof Key for Code Exchange) to prevent authorization code interception, RFC 9728 Protected Resource Metadata for auth server discovery, and RFC 8707 Resource Indicators to bind tokens to specific servers. For stdio-based (local) transports, MCP recommends pulling credentials from the environment rather than using OAuth.

Can I use Auth0 or Okta with my MCP server?

Yes. The MCP spec is designed to work with any OAuth 2.1-compliant authorization server, including Auth0, Okta, Keycloak, and Azure AD. Your identity provider needs to expose authorization server metadata, support PKCE with the S256 challenge method, and issue tokens with the correct audience claim matching your MCP server's canonical URI.

Related Resources

Fast.io features

Give Your AI Agents Persistent Storage

Fast.io's official MCP server includes built-in OAuth 2.1 authentication, so your AI agents can securely access cloud storage without building auth from scratch.