Files
2025-11-30 08:24:23 +08:00

9.1 KiB

MCP Authentication Patterns - Comparison Matrix

This document compares all 4 authentication patterns supported by Cloudflare MCP servers.


Quick Reference Table

Pattern Security Complexity Use Case Client Setup
No Auth ⚠️ None Simple Internal tools, dev Just URL
Token Validation Good Medium Custom auth, API keys Bearer token
OAuth Proxy Excellent Medium GitHub, Google, Azure OAuth flow
Full OAuth Provider Maximum Complex Custom identity Full OAuth 2.1

Pattern 1: No Authentication

When to Use

  • Internal tools (private network only)
  • Development and testing
  • Public APIs (intentionally open)

Security

⚠️ WARNING: Anyone with the URL can access your MCP server

Implementation

export class MyMCP extends McpAgent<Env> {
  // No authentication required
}

export default {
  fetch(request, env, ctx) {
    return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
  }
};

Client Configuration

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.workers.dev/sse"
    }
  }
}

Pros

Simplest to implement No OAuth flow complexity Fast to test

Cons

No security Anyone can use your server Can't identify users


Pattern 2: Token Validation (JWT)

When to Use

  • Pre-authenticated clients
  • Custom authentication systems
  • API key-based access
  • Service-to-service communication

Security

GOOD: Secure if tokens properly managed

Implementation

import { JWTVerifier } from "agents/mcp";

const verifier = new JWTVerifier({
  secret: env.JWT_SECRET,
  issuer: "your-auth-server",
  audience: "your-mcp-server"
});

export default {
  async fetch(request, env, ctx) {
    // Verify token before serving MCP requests
    const token = request.headers.get("Authorization")?.replace("Bearer ", "");

    if (!token) {
      return new Response("Unauthorized", { status: 401 });
    }

    try {
      const payload = await verifier.verify(token);
      // Token valid, serve MCP
      return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
    } catch (error) {
      return new Response("Invalid token", { status: 403 });
    }
  }
};

Client Configuration

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.workers.dev/sse",
      "headers": {
        "Authorization": "Bearer YOUR_JWT_TOKEN_HERE"
      }
    }
  }
}

Pros

Simple integration with existing auth No OAuth flow needed Works with any JWT issuer

Cons

Token management (refresh, expiry) Manual token distribution Client must handle token lifecycle


Pattern 3: OAuth Proxy (workers-oauth-provider)

When to Use

  • Integrate with GitHub, Google, Azure, etc.
  • User-scoped tools (read/write GitHub repos)
  • Need user identity in tools
  • Production applications

Security

EXCELLENT: Industry-standard OAuth 2.1

Implementation

import { OAuthProvider, GitHubHandler } from "@cloudflare/workers-oauth-provider";

export default new OAuthProvider({
  authorizeEndpoint: "/authorize",
  tokenEndpoint: "/token",
  clientRegistrationEndpoint: "/register",

  defaultHandler: new GitHubHandler({
    clientId: (env) => env.GITHUB_CLIENT_ID,
    clientSecret: (env) => env.GITHUB_CLIENT_SECRET,
    scopes: ["repo", "user:email"],

    context: async (accessToken) => {
      const octokit = new Octokit({ auth: accessToken });
      const { data: user } = await octokit.rest.users.getAuthenticated();

      return {
        login: user.login,
        email: user.email,
        accessToken
      };
    }
  }),

  kv: (env) => env.OAUTH_KV,

  apiHandlers: {
    "/sse": MyMCP.serveSSE("/sse"),
    "/mcp": MyMCP.serve("/mcp")
  },

  allowConsentScreen: true,
  allowDynamicClientRegistration: true
});

Client Configuration

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.workers.dev/sse",
      "auth": {
        "type": "oauth",
        "authorizationUrl": "https://my-mcp.workers.dev/authorize",
        "tokenUrl": "https://my-mcp.workers.dev/token"
      }
    }
  }
}

Required Bindings

{
  "kv_namespaces": [
    { "binding": "OAUTH_KV", "id": "YOUR_KV_ID" }
  ],
  "vars": {
    "GITHUB_CLIENT_ID": "optional-preconfig",
    "GITHUB_CLIENT_SECRET": "optional-preconfig"
  }
}

Pros

Standard OAuth 2.1 flow User identity in tools (this.props.login) Automatic token management Works with multiple providers Dynamic Client Registration (no pre-config needed) Consent screen for permissions

Cons

Requires KV namespace More complex than token validation OAuth flow adds latency on first connect


Pattern 4: Full OAuth Provider

When to Use

  • You ARE the identity provider
  • Custom consent screens
  • Full control over auth flow
  • Enterprise B2B applications

Security

MAXIMUM: Complete control over security

Implementation

Complex - requires full OAuth 2.1 server implementation.

See Cloudflare's remote-mcp-authkit template for example.

Client Configuration

Same as Pattern 3

Pros

Full control over authentication Custom user management Custom consent screens Fine-grained permissions Works with any OAuth client

Cons

Very complex to implement Must handle OAuth 2.1 spec correctly Token management, refresh, expiry User database required Audit logs recommended


Supported OAuth Providers

GitHub

Handler: GitHubHandler Scopes: repo, user:email, read:org, write:org Example: templates/mcp-oauth-proxy.ts

Google

Handler: GoogleHandler Scopes: openid, email, profile, https://www.googleapis.com/auth/drive.readonly Setup: See references/oauth-providers.md

Azure AD

Handler: AzureADHandler Scopes: openid, email, User.Read Setup: See references/oauth-providers.md

Custom Provider

Handler: GenericOAuthHandler Use case: Any OAuth 2.1 compliant provider


Migration Path

From No Auth → Token Validation

  1. Generate JWT signing key
  2. Add JWTVerifier middleware
  3. Issue tokens to clients
  4. Update client config with Authorization header

From Token Validation → OAuth Proxy

  1. Choose OAuth provider (GitHub, Google, etc.)
  2. Add KV namespace binding
  3. Replace fetch handler with OAuthProvider
  4. Update client config with OAuth URLs
  5. Remove Authorization headers

From OAuth Proxy → Full OAuth Provider

  1. Implement OAuth 2.1 server logic
  2. Add user database (D1, KV, external)
  3. Implement consent screen UI
  4. Implement token refresh logic
  5. Add audit logging

Security Best Practices

All Patterns

Use HTTPS in production (automatic on Cloudflare) Validate all inputs (Zod schemas) Log authentication attempts Rate limit authentication endpoints

Token Validation

Use strong secrets (256-bit minimum) Short token expiry (15-60 minutes) Implement token refresh Rotate secrets regularly

OAuth Patterns

Always use allowConsentScreen: true in production Request minimal scopes needed Validate redirect URIs Use PKCE for authorization code flow Store tokens securely (KV, encrypted)

Full OAuth Provider

Implement OAuth 2.1 spec correctly Use authorization code flow (not implicit) Validate all OAuth parameters Implement token introspection Add audit logging for all auth events


Common Mistakes

allowConsentScreen: false  // ❌ NEVER in production

Users won't see what permissions they're granting!

Storing Secrets in Code

const secret = "my-secret-key";  // ❌ NEVER commit secrets

Use environment variables or secrets management.

Overly Broad Scopes

scopes: ["repo", "delete_repo", "admin:org"]  // ❌ Too powerful

Request minimal scopes needed.

No Token Validation

// ❌ Trusting token without verification
const token = request.headers.get("Authorization");
// Use token without verifying...

Always validate tokens before use.


Testing Authentication

Test No Auth

curl https://my-mcp.workers.dev/sse
# Should connect immediately

Test Token Validation

# Without token (should fail)
curl https://my-mcp.workers.dev/sse

# With token (should succeed)
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://my-mcp.workers.dev/sse

Test OAuth Flow

# Use MCP Inspector
npx @modelcontextprotocol/inspector@latest

# Or use Claude Desktop (will trigger OAuth flow)

Further Reading