OAuth 2.1
Production-ready OAuth 2.1 authentication with JWKS, token introspection, and support for Auth0, Okta, AWS Cognito, and Azure AD
OAuth 2.1 Authentication
MCP Framework supports OAuth 2.1 authentication per the MCP specification (2025-06-18), including Protected Resource Metadata (RFC 9728) and proper token validation with JWKS support.
OAuth authentication works with both SSE and HTTP Stream transports and supports two validation strategies.
Recommended for Production
OAuth 2.1 is the recommended authentication method for production deployments. For simpler use cases, see API Key and JWT authentication.
Quick Start
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: ["https://auth.example.com"],
resource: "https://mcp.example.com",
validation: {
type: 'jwt',
jwksUri: "https://auth.example.com/.well-known/jwks.json",
audience: "https://mcp.example.com",
issuer: "https://auth.example.com"
}
})
}
}
}
});
await server.start();Testing Your Setup
# 1. Check metadata endpoint
curl http://localhost:8080/.well-known/oauth-protected-resource
# 2. Test without token (should return 401)
curl -v -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
# 3. Test with valid token
curl -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'Token Validation Strategies
MCP Framework supports two token validation strategies, each with different trade-offs.
JWT Validation (Recommended for Performance)
JWT validation fetches public keys from your authorization server's JWKS endpoint and validates tokens locally. This is the fastest option as it doesn't require a round-trip to the auth server for each request.
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [
process.env.OAUTH_AUTHORIZATION_SERVER
],
resource: process.env.OAUTH_RESOURCE,
validation: {
type: 'jwt',
jwksUri: process.env.OAUTH_JWKS_URI,
audience: process.env.OAUTH_AUDIENCE,
issuer: process.env.OAUTH_ISSUER,
algorithms: ['RS256', 'ES256'] // Optional (default: ['RS256', 'ES256'])
}
}),
endpoints: {
initialize: true, // Protect session initialization
messages: true // Protect MCP messages
}
}
}
}
});Environment Variables:
OAUTH_AUTHORIZATION_SERVER=https://auth.example.com
OAUTH_RESOURCE=https://mcp.example.com
OAUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.json
OAUTH_AUDIENCE=https://mcp.example.com
OAUTH_ISSUER=https://auth.example.comPerformance characteristics:
- First request (cache miss): ~150-200ms
- Cached requests: ~5-10ms
- JWKS cache TTL: 15 minutes (configurable)
Token Introspection (Recommended for Centralized Control)
Token introspection validates tokens by calling your authorization server's introspection endpoint (RFC 7662). This provides centralized control and is useful when you need real-time token revocation.
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "sse",
options: {
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [
process.env.OAUTH_AUTHORIZATION_SERVER
],
resource: process.env.OAUTH_RESOURCE,
validation: {
type: 'introspection',
audience: process.env.OAUTH_AUDIENCE,
issuer: process.env.OAUTH_ISSUER,
introspection: {
endpoint: process.env.OAUTH_INTROSPECTION_ENDPOINT,
clientId: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET
}
}
})
}
}
}
});Environment Variables:
OAUTH_AUTHORIZATION_SERVER=https://auth.example.com
OAUTH_RESOURCE=https://mcp.example.com
OAUTH_AUDIENCE=https://mcp.example.com
OAUTH_ISSUER=https://auth.example.com
OAUTH_INTROSPECTION_ENDPOINT=https://auth.example.com/oauth/introspect
OAUTH_CLIENT_ID=mcp-server
OAUTH_CLIENT_SECRET=your-client-secretPerformance characteristics:
- First request (cache miss): ~200-300ms
- Cached requests: ~20-50ms
- Cache TTL: 5 minutes (configurable)
Choosing a Strategy
| Factor | JWT Validation | Token Introspection |
|---|---|---|
| Performance | Excellent (~5-10ms cached) | Good (~20-50ms cached) |
| Token Revocation | Delayed (until expiry) | Immediate |
| Auth Server Load | Very low | Moderate |
| Network Dependency | Low (after key fetch) | High (every validation) |
| Best For | High-performance APIs, short-lived tokens | Real-time revocation, compliance |
Recommendation: Use JWT validation for most use cases. Use token introspection when you need real-time revocation.
Features
- RFC 9728 Compliance: Automatic Protected Resource Metadata endpoint at
/.well-known/oauth-protected-resource - RFC 6750 WWW-Authenticate Headers: Proper OAuth error responses with challenge headers
- JWKS Key Caching: Public keys cached for 15 minutes (configurable)
- Token Introspection Caching: Introspection results cached for 5 minutes (configurable)
- Security: Tokens in query strings are automatically rejected
- Claims Extraction: Access token claims in your tool handlers via
AuthResult
Provider Integration
The OAuth provider works with any RFC-compliant OAuth 2.1 authorization server.
Setup:
- Create a Machine to Machine Application in Auth0 Dashboard
- Note your tenant domain, JWKS URI, and API audience
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [`https://${process.env.AUTH0_DOMAIN}`],
resource: process.env.AUTH0_AUDIENCE,
validation: {
type: 'jwt',
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
audience: process.env.AUTH0_AUDIENCE,
issuer: `https://${process.env.AUTH0_DOMAIN}/`
}
})
}
}
}
});AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_AUDIENCE=https://mcp.example.comGet a test token:
curl --request POST \
--url https://your-tenant.auth0.com/oauth/token \
--header 'content-type: application/json' \
--data '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "https://mcp.example.com",
"grant_type": "client_credentials"
}'Setup:
- Create an API Services app in Okta Admin Console
- Use the "default" authorization server or create a custom one
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [process.env.OKTA_ISSUER],
resource: process.env.OKTA_AUDIENCE,
validation: {
type: 'jwt',
jwksUri: `${process.env.OKTA_ISSUER}/v1/keys`,
audience: process.env.OKTA_AUDIENCE,
issuer: process.env.OKTA_ISSUER
}
})
}
}
}
});OKTA_ISSUER=https://your-domain.okta.com/oauth2/default
OKTA_AUDIENCE=api://mcp-serverSetup:
- Create a User Pool in AWS Cognito Console
- Configure an app client with client credentials flow
- Optionally create a Resource Server for custom scopes
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [process.env.COGNITO_ISSUER],
resource: process.env.COGNITO_AUDIENCE,
validation: {
type: 'jwt',
jwksUri: `${process.env.COGNITO_ISSUER}/.well-known/jwks.json`,
audience: process.env.COGNITO_AUDIENCE,
issuer: process.env.COGNITO_ISSUER
}
})
}
}
}
});COGNITO_ISSUER=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX
COGNITO_AUDIENCE=1234567890abcdefghijklmnopSetup:
- Register an application in Azure Portal
- Configure "Expose an API" with an Application ID URI
- Create a client secret under "Certificates & secrets"
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
port: 8080,
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [
`https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`
],
resource: process.env.AZURE_AUDIENCE,
validation: {
type: 'jwt',
jwksUri: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/discovery/v2.0/keys`,
audience: process.env.AZURE_AUDIENCE,
issuer: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`
}
})
}
}
}
});AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
AZURE_AUDIENCE=api://mcp-serverAdvanced Configuration
Custom Caching
Adjust cache TTLs for your use case:
import { JWTValidator, IntrospectionValidator } from "mcp-framework";
// Custom JWT validator with shorter cache
const jwtValidator = new JWTValidator({
jwksUri: "https://auth.example.com/.well-known/jwks.json",
audience: "https://mcp.example.com",
issuer: "https://auth.example.com",
cacheTTL: 600000 // 10 minutes (default: 15 minutes)
});
// Custom introspection validator with longer cache
const introspectionValidator = new IntrospectionValidator({
endpoint: "https://auth.example.com/oauth/introspect",
clientId: "mcp-server",
clientSecret: process.env.CLIENT_SECRET,
cacheTTL: 600000 // 10 minutes (default: 5 minutes)
});Multiple Authorization Servers
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [
"https://primary-auth.example.com",
"https://partner-auth.example.com"
],
resource: "https://mcp.example.com",
validation: {
type: 'jwt',
jwksUri: "https://primary-auth.example.com/.well-known/jwks.json",
audience: "https://mcp.example.com",
issuer: "https://primary-auth.example.com"
}
})
}
}
}
});Per-Endpoint Authentication
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
auth: {
provider: new OAuthAuthProvider({ /* ... */ }),
endpoints: {
initialize: true, // Require auth for session creation
messages: true // Require auth for MCP messages
}
}
}
}
});Security Best Practices
- Always use HTTPS in production — OAuth tokens should never be transmitted over unencrypted connections
- Validate audience claims — Prevents token reuse across different services
- Use short-lived tokens — Reduces risk if tokens are compromised (15-60 minutes recommended)
- Enable token introspection caching — Reduces load on authorization server while maintaining security
- Monitor token errors — Track failed authentication attempts for security insights
- Never store tokens in localStorage — Use secure, httpOnly cookies or secure storage
Migration Guide
From JWT Provider to OAuth
// Before (JWT Provider)
import { MCPServer, JWTAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "sse",
options: {
auth: {
provider: new JWTAuthProvider({
secret: process.env.JWT_SECRET,
algorithms: ["HS256"]
})
}
}
}
});
// After (OAuth Provider)
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "sse",
options: {
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [process.env.OAUTH_ISSUER],
resource: process.env.OAUTH_RESOURCE,
validation: {
type: 'jwt',
jwksUri: process.env.OAUTH_JWKS_URI,
audience: process.env.OAUTH_AUDIENCE,
issuer: process.env.OAUTH_ISSUER
}
})
}
}
}
});Key differences:
- OAuth uses asymmetric keys (RS256/ES256) instead of symmetric (HS256)
- Tokens must come from a proper authorization server
- Automatic metadata endpoint at
/.well-known/oauth-protected-resource - Better security with audience validation
From API Key to OAuth
// Before (API Key)
import { MCPServer, APIKeyAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
auth: {
provider: new APIKeyAuthProvider({
keys: [process.env.API_KEY]
})
}
}
}
});
// After (OAuth)
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
const server = new MCPServer({
transport: {
type: "http-stream",
options: {
auth: {
provider: new OAuthAuthProvider({
authorizationServers: [process.env.OAUTH_ISSUER],
resource: process.env.OAUTH_RESOURCE,
validation: {
type: 'jwt',
jwksUri: process.env.OAUTH_JWKS_URI,
audience: process.env.OAUTH_AUDIENCE,
issuer: process.env.OAUTH_ISSUER
}
})
}
}
}
});Troubleshooting
Common Issues
| Error | Cause | Solution |
|---|---|---|
| "Invalid token signature" | JWKS keys don't match token | Verify JWKS endpoint returns correct keys; check kid in token header |
| "Token audience invalid" | Token aud claim mismatch | Ensure audience config matches the token's aud claim |
| "Token has expired" | Token exp is in the past | Request a new token; check system clock sync |
| "JWKS endpoint unreachable" | Network or wrong URI | Test endpoint with curl; check DNS and firewall |
| "Token introspection failed" | Bad credentials or endpoint | Verify clientId, clientSecret, and introspection endpoint URL |
Debug Logging
# Enable debug logging
MCP_DEBUG_CONSOLE=true node dist/index.js
# Enable file logging
MCP_ENABLE_FILE_LOGGING=true MCP_LOG_DIRECTORY=logs node dist/index.jsLook for OAuth-related log messages:
[INFO] OAuthAuthProvider initialized with JWT validation
[DEBUG] Token claims - sub: user-123, scope: read write
[ERROR] OAuth authentication failed: Token has expired