Announcing CIMD support for MCP Client registration
Learn more
MCP authentication
Jul 25, 2025

Implementing OAuth for MCP servers: A developer’s guide

The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external systems. But as your MCP server moves from prototype to production, one question becomes critical: How do you secure it with an auth and identity layer?

This guide walks you through implementing OAuth 2.1 authentication for your MCP server using Scalekit. We'll cover the complete implementation process, from initial setup to production deployment.

Implementation overview

Whether you’re building a brand new MCP server or retrofitting an existing one, OAuth implementation involves four steps.

  1. Register your MCP Server and configure appropriate OAuth scopes
  2. Implement OAuth protected resource metadata (/.well-known/oauth-protected-resource)
  3. Validate JWT tokens
  4. (Optional) Verify scopes as part of JWT token

Let's walk through each step with practical code examples. You can also follow through with our video demo below.

1. Register your MCP server

Head over to the Scalekit dashboard to register your MCP server. Think of this step as provisioning your server’s identity and defining what permissions it can grant to clients.

Basic configuration

  • Server name: A user-friendly name (e.g., "Weather Assistant API")
  • Resource identifier: Your MCP server’s unique identifier, typically your server’s URL (e.g., https://api.weather-assistant.com)
  • Server logo: Upload a 45x45 pixel logo to help users recognize your service

Access control settings

  • Dynamic client registration: Enables MCP clients to register automatically
  • Offline access: Allow refresh tokens for long-term access
  • Token configuration:
    • Access tokens: 300-3600 seconds (5 minutes to 1 hour)
    • Refresh tokens: 300-86400 seconds (5 minutes to 24 hours)

Define scopes

Plan your scopes carefully so you can control which actions require which permissions. Examples:

  • mcp:tools:weather:read- Read weather data
  • mcp:tools:calendar:write - Modify calendar events
  • mcp:resources:customer-data:read - Access customer information
  • mcp:exec:workflows:* - Execute any workflow

2. Implement OAuth protected resource metadata

MCP clients discover your authorization server through the OAuth protected resource metadata endpoint. This is your server's "business card" for OAuth clients.

OAuth relies on metadata discovery so clients and tools can integrate without hard-coding endpoints. Your MCP server needs to expose an OAuth Protected Resource Metadata endpoint.

Example

// Required: OAuth Protected Resource Metadata endpoint app.get('/.well-known/oauth-protected-resource', (req, res) => { res.json({ resource: 'https://your-mcp-server.com', authorization_servers: ['https://your-org.scalekit.com'], bearer_methods_supported: ['header'], resource_documentation: 'https://your-mcp-server.com/docs', scopes_supported: [ 'mcp:tools:weather', 'mcp:tools:calendar:read', 'mcp:tools:calendar:write', 'mcp:tools:email:send', 'mcp:resources:*' ] }); }); // Optional: Proxy authorization server metadata for legacy clients app.get('/.well-known/oauth-authorization-server', async (req, res) => { try { const response = await fetch( 'https://your-org.scalekit.com/.well-known/oauth-authorization-server' ); const metadata = await response.json(); res.json(metadata); } catch (error) { res.status(500).json({ error: 'Failed to fetch authorization server metadata' }); } });

Key metadata fields

  • resource: Your MCP server's unique identifier (matches the aud claim in JWT tokens)
  • authorization_servers: List of trusted authorization servers
  • bearer_methods_supported: How tokens are transmitted (typically header)
  • scopes_supported: Available permissions for clients to request

You can find a complete reference format in the docs.

3. Implement JWT token validation

This is the heart of your security implementation. Every request to your MCP server should validate the incoming JWT token. Once your server is registered and metadata is available, every incoming request will include a Bearer token in the Authorization header.

Security best practices

  • Always validate the issuer - Ensure tokens come from your trusted authorization server
  • Check the audience - Verify tokens are intended for your server
  • Handle errors gracefully - Return proper WWW-Authenticate headers for client guidance
  • Log security events - Monitor failed authentication attempts

Example (Node.js/Pseudocode):

import { jwtVerify, createRemoteJWKSet } from 'jose'; // Configure JWKS endpoint from your Scalekit instance const JWKS = createRemoteJWKSet( new URL('https://your-org.scalekit.com/.well-known/jwks') ); // WWW-Authenticate header for 401 responses const WWW_AUTHENTICATE_HEADER = [ 'Bearer error="unauthorized"', 'error_description="Authorization required"', `resource_metadata="https://your-mcp-server.com/.well-known/oauth-protected-resource"` ].join(', '); const validateToken = async (req, res, next) => { const authHeader = req.headers.authorization; const token = authHeader?.match(/^Bearer (.+)$/)?.[1]; if (!token) { return res .set('WWW-Authenticate', WWW_AUTHENTICATE_HEADER) .status(401) .json({ error: 'unauthorized', error_description: 'Bearer token required' }); } try { const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://your-org.scalekit.com', audience: 'https://your-mcp-server.com' // Your MCP server identifier }); // Attach token claims to request for downstream use req.auth = { userId: payload.sub, scopes: payload.scope?.split(' ') || [], clientId: payload.client_id, expiresAt: payload.exp }; next(); } catch (error) { console.error('Token validation failed:', error.message); return res .set('WWW-Authenticate', WWW_AUTHENTICATE_HEADER) .status(401) .json({ error: 'invalid_token', error_description: 'Bearer token is invalid or expired' }); } }; // Apply to all MCP endpoints app.use('/mcp', validateToken);

Feel free to adapt this pattern to your preferred language or framework.

4. Implement scope-based authorization

OAuth scopes enable fine-grained permission control. Validating the token’s signature proves the request is from an authenticated client, but you often also need to check if it has permission to perform a specific action. Here's how to implement scope checking:

const requireScope = (requiredScope) => { return (req, res, next) => { const userScopes = req.auth.scopes || []; // Check for exact scope match or wildcard permissions const hasScope = userScopes.some(scope => scope === requiredScope || scope.endsWith(':*') && requiredScope.startsWith(scope.slice(0, -1)) ); if (!hasScope) { return res.status(403).json({ error: 'insufficient_scope', error_description: `Required scope: ${requiredScope}`, scope: requiredScope }); } next(); }; }; // Example: Protect specific MCP tools with scopes app.post('/mcp/tools/weather', requireScope('mcp:tools:weather'), handleWeatherRequest ); app.post('/mcp/tools/calendar', requireScope('mcp:tools:calendar:read'), handleCalendarRead ); app.post('/mcp/tools/send-email', requireScope('mcp:tools:email:send'), handleEmailSend );

Testing your implementation

Create a comprehensive test suite to verify your OAuth implementation:

const testMCPAuth = async () => { // 1. Test token acquisition const tokenResponse = await fetch('https://your-org.scalekit.com/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: 'your-test-client-id', client_secret: 'your-test-client-secret', scope: 'mcp:tools:weather' }) }); const { access_token } = await tokenResponse.json(); // 2. Test authorized request const mcpResponse = await fetch('https://your-mcp-server.com/mcp/tools/weather', { method: 'POST', headers: { 'Authorization': `Bearer ${access_token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ method: 'weather/get_forecast', params: { location: 'San Francisco' } }) }); console.log('MCP Response:', await mcpResponse.json()); // 3. Test unauthorized request (wrong scope) const unauthorizedResponse = await fetch('https://your-mcp-server.com/mcp/tools/calendar', { method: 'POST', headers: { 'Authorization': `Bearer ${access_token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ method: 'calendar/list_events', params: {} }) }); console.log('Should be 403:', unauthorizedResponse.status); };

Production considerations

  • Rate limiting: Implement rate limiting on token validation endpoints
  • Token introspection: Consider implementing token introspection for high-security scenarios
  • Audit logging: Log all authentication and authorization events
  • Token rotation: Implement proper refresh token rotation
  • Token caching: Cache JWKS Public Keys for 24 hours to reduce http calls and make token signature verification instantaneous
  • Connection pooling: Reuse HTTP connections for token validation
  • Async processing: Use async/await patterns for non-blocking operations

Error handling

// Comprehensive error handling const handleAuthError = (error, req, res, next) => { if (error.code === 'ERR_JWT_EXPIRED') { return res.status(401).json({ error: 'token_expired', error_description: 'The access token has expired' }); } if (error.code === 'ERR_JWT_INVALID') { return res.status(401).json({ error: 'invalid_token', error_description: 'The access token is malformed' }); } // Generic error response return res.status(500).json({ error: 'server_error', error_description: 'An internal server error occurred' }); }; app.use(handleAuthError);

Conclusion

Implementing OAuth for your MCP server transforms it from a prototype into a production-ready service. The four-step process—registration, metadata implementation, token validation, and scope verification — provides a solid foundation for secure AI integrations.

With OAuth properly implemented, your MCP server can safely integrate with enterprise systems, handle sensitive data, and scale to meet production demands. The investment in proper authorization pays dividends in security, compliance, and user trust.

Ready to move forward? Consider Scalekit as drop-in OAuth authorization server for your MCP servers

If you want a deeper dive or ready-to-use code samples, explore our full guide here.

Building an OAuth 2.1 authorization layer for your MCP server means more than issuing access tokens—you need dynamic client registration, PKCE, token introspection, scoped short‑lived tokens and detailed audit logs  .  Rather than implementing these features from scratch, sign up for a free Scalekit account to add a drop‑in OAuth server that supports all of them out‑of‑the‑box and secure your agentic workflows.  Have questions about migrating your MCP server to OAuth 2.1? Book time with our experts for a step‑by‑step walkthrough.

FAQs

Why is OAuth 2.1 essential for Model Context Protocol servers?

Implementing OAuth 2.1 transforms an MCP server from a prototype into a production ready service capable of handling sensitive enterprise data. It provides a standardized framework for authentication and authorization ensuring that AI applications interact securely with external systems. By adopting OAuth 2.1 you enable critical security features like scoped access control and short lived tokens which are vital for protecting agentic workflows. Scalekit simplifies this transition by offering a drop in authorization server that manages complex identity requirements out of the box allowing teams to focus on core AI functionality.

How does the OAuth protected resource metadata endpoint function?

The protected resource metadata endpoint serves as a discovery mechanism for MCP clients to identify the correct authorization server. By exposing configuration at the well known path clients can dynamically retrieve details such as supported scopes and bearer methods without hard coding sensitive integration logic. This standard based approach ensures that your MCP server is interoperable and easily discoverable by various AI agents and tools. It essentially acts as a business card for your server defining the rules for how clients should request permissions and handle tokens when interacting with your secure resources.

What are the primary steps for implementing MCP server security?

Securing an MCP server involves four critical steps starting with registering the server in the Scalekit dashboard to define its identity and permissions. Next you must implement the OAuth protected resource metadata endpoint to enable client discovery. The third step requires robust JWT token validation to ensure every incoming request is authenticated against trusted issuers and intended for your specific audience. Finally you should implement fine grained scope based authorization to control access to specific tools and resources. This comprehensive architecture ensures that your AI integrations are secure compliant and ready for enterprise scale deployment.

Why should developers validate the issuer and audience in JWTs?

Validating the issuer and audience claims within a JWT is a fundamental security practice to prevent unauthorized access. The issuer check ensures that the token was strictly generated by your trusted Scalekit authorization server while the audience check verifies that the token was intended specifically for your MCP server. Skipping these validations could expose your server to token substitution attacks where a valid token from a different service is used maliciously. By strictly enforcing these checks you guarantee that only legitimate requests from authorized clients are processed protecting your system from identity spoofing and unauthorized data access.

How do scopes enable fine grained access control for AI?

Scopes allow you to define granular permissions that dictate exactly what an AI client can do within your MCP server. Instead of providing broad access you can create specific scopes like weather read or calendar write to restrict agents to only the necessary functions. This principle of least privilege is essential for maintaining security in complex B2B environments where different agents may require different levels of data access. Scalekit enables you to map these scopes directly to your API endpoints ensuring that every request is authorized based on the specific permissions granted during the initial authentication flow.

What benefits does Dynamic Client Registration provide for MCP clients?

Dynamic Client Registration or DCR allows MCP clients to register with the authorization server automatically instead of requiring manual provisioning. This is particularly useful in scalable AI ecosystems where new agents or tools may need to connect to your MCP server dynamically. By enabling DCR you reduce administrative overhead and streamline the onboarding process for third party integrations while still maintaining strict security standards. Scalekit supports DCR out of the box allowing your MCP architecture to scale efficiently without compromising the control or visibility needed by CISOs and engineering managers in enterprise environments.

Why is JWKS caching important for MCP server performance?

Caching JSON Web Key Sets or JWKS is a critical optimization for any production MCP server. By localizing the public keys used for token signature verification you eliminate the need for an external HTTP call on every incoming request. This significantly reduces latency and ensures that token validation is nearly instantaneous which is vital for high performance AI applications. We recommend caching these keys for up to twenty four hours while still having a mechanism to refresh them if keys are rotated. This architectural choice balances high security with the low latency requirements of modern agentic workflows and real time integrations.

How can engineering managers handle token expiration and rotation?

Managing token lifecycles is essential for balancing security and user experience in B2B auth. Engineering managers should configure short lived access tokens to minimize the impact of token theft while using refresh tokens to maintain long term access for authorized agents. Implementing refresh token rotation adds an extra layer of security by ensuring that each refresh token is used only once. Scalekit provides robust configuration options for token durations and rotation policies allowing you to tailor security settings to your specific compliance needs. This approach ensures that your MCP server remains secure while providing a seamless experience for AI agents.

What production considerations are vital for securing MCP servers?

Transitioning an MCP server to production requires more than just basic authentication. CISOs and CTOs must consider rate limiting to prevent abuse and comprehensive audit logging to track all authentication and authorization events for compliance. Additionally implementing token introspection can provide real time validation for high security scenarios. It is also important to handle errors gracefully by returning standardized WWW Authenticate headers to guide clients during authentication failures. By addressing these production considerations with a tool like Scalekit you ensure that your AI infrastructure is not only secure but also resilient observable and ready for enterprise grade workloads.

No items found.
Ready to add auth to your MCP servers?
On this page
Share this article
Ready to add auth to your MCP servers?

Acquire enterprise customers with zero upfront cost

Every feature unlocked. No hidden fees.
Start Free
$0
/ month
1 million Monthly Active Users
100 Monthly Active Organizations
1 SSO connection
1 SCIM connection
10K Connected Accounts
Unlimited Dev & Prod environments