OAuth authorization server for MCP servers
Secure your Model Context Protocol (MCP) servers with Scalekit’s drop-in OAuth 2.1 authorization solution
Scalekit provides a production-ready OAuth 2.1 authorization server that implements the MCP authorization specification. This guide shows you how to integrate OAuth-based authentication and authorization into your MCP server with minimal code changes.
Why use Scalekit OAuth for MCP servers?
- Identity-scoped access: Restrict each token to a specific user or agent.
- Granular permissions: Control exactly which tools and data each client can access using fine-grained scopes.
- OAuth 2.1 compliance: Rely on a modern, secure, and widely adopted authorization standard.
- Comprehensive audit trails: Track who accessed what, when, and with which permissions.
For a deeper explanation of why an OAuth layer is essential for remote MCP servers, read the MCP authorization overview.
How it works
Section titled “How it works”The Scalekit OAuth authorization server and your MCP server work together to secure access and enforce permissions.
Scalekit OAuth authorization server
Acts as the identity provider for your MCP server. It:
- Authenticates users and agents
- Issues access tokens with fine-grained scopes
- Manages OAuth 2.1 flows (authorization code, client credentials)
- Supports dynamic client registration for easy onboarding
Your MCP server
Validates incoming access tokens and enforces the permissions encoded in each token. Only requests with valid, authorized tokens are allowed.
This separation of responsibilities ensures a clear boundary: Scalekit handles identity and token issuance, while your MCP server focuses on business logic of executing the actual tool calls.
Getting started
Section titled “Getting started”Prerequisites
Section titled “Prerequisites”Before you begin, ensure you have:
- Access to your Scalekit account and the API credentials. If you don’t have a Scalekit account yet, you can signup here.
- Installed Scalekit SDK into your project
npm install @scalekit-sdk/node
pip install scalekit-sdk-python
Step 1: Register your MCP server
Section titled “Step 1: Register your MCP server”In the Scalekit dashboard, navigate to MCP servers and click Add MCP server. Configure your server with the following settings:
Basic configuration:
- Server name: A display name that users will see during authorization (e.g., “My AI Assistant”)
- Resource identifier: Your MCP server’s unique identifier, typically your server’s URL (e.g.,
https://mcp.yourapp.com
). Access tokens minted by Scalekit will have the resource identifier asaud
claim.
Access control settings:
- Allow dynamic client registration — Enables MCP clients to register automatically without manual approval. For security reasons, Scalekit ensures that any client that registers via DCR has to implement PKCE-based OAuth 2.1 flow to prevent authorization code interception attacks.
Token configuration:
- Access token lifetime: Recommended 300-3600 seconds (5 minutes to 1 hour)
Authentication Provider:
-
Use Scalekit: If you are already using Scalekit to power authentication for your other resources like web application, mobile application, API etc, you can continue to use Scalekit as the authentication provider for your MCP server too.
-
Use your own authentication provider: If you are using any other authentication provider like Microsoft Entra, Google Workspace, AWS Cognito, Auth0, Keycloak etc, you can configure Scalekit to integrate with your existing auth system to validate user identity.
Step 2: Implement resource metadata discovery
Section titled “Step 2: Implement resource metadata discovery”Once you have added your MCP server in the Scalekit dashboard, you will be presented with the protected resource metadata information that you can copy directly from the Scalekit Dashboard and implement in your MCP Server.
MCP clients discover your authorization server through the OAuth 2.0 protected resource metadata endpoint.
Below is the code sample to implement resource metadata discovery in your MCP Server:
// OAuth Protected Resource Metadata endpoint - Required for MCP client discovery// This endpoint provides metadata about your OAuth protected resource, including supported scopes and token types.// Replace the placeholder URL with the actual authorization server URL from your Scalekit dashboard.// Example: https://yourapp.scalekit.com/.well-known/oauth-protected-resourceapp.get('/.well-known/oauth-protected-resource', (req, res) => { res.json({ "authorization_servers": [ "https://<SCALEKIT_ENVIRONMENT_URL>/resources/res_82829009141891595" ], "bearer_methods_supported": [ "header" ], "resource": "https://mcp.yourapp.com", "resource_documentation": "https://mcp.yourapp.com/docs", "scopes_supported": ["weather:read", "weather:write"] });});
5 collapsed lines
from fastapi import FastAPIfrom fastapi.responses import JSONResponse
app = FastAPI()
# OAuth Protected Resource Metadata endpoint - Required for MCP client discovery# Copy the actual authorization server URL and metadata from your Scalekit dashboard.# The values shown here are examples - replace with your actual configuration.@app.get("/.well-known/oauth-protected-resource")async def get_oauth_protected_resource(): return JSONResponse({ "authorization_servers": [ "https://<SCALEKIT_ENVIRONMENT_URL>/resources/res_82829009141891595" ], "bearer_methods_supported": [ "header" ], "resource": "https://mcp.yourapp.com", "resource_documentation": "https://mcp.yourapp.com/docs", "scopes_supported": ["weather:read", "weather:write"] })
Description of the OAuth Protected Resource Metadata:
Field | Description |
---|---|
resource | The identifier of the resource server. This is the unique identifier of your MCP Server. Every token that Scalekit issues will have this value as aud claim. |
authorization_servers | A list of authorization servers that are trusted by the resource server. MCP Clients use this information to discover more about the authorization server capability by fetching the authorization server. |
bearer_methods_supported | A list of methods that are supported for bearer tokens. MCP Clients send the OAuth token as Authorization: Bearer <token> header. |
resource_documentation | A URL to the documentation of the resource server. |
scopes_supported | A list of scopes that are supported by the resource server. MCP Clients use this information to determine which scopes that they would like the token for as part of the OAuth authorize request. |
Step 3: Validate Bearer Token in your MCP Server
Section titled “Step 3: Validate Bearer Token in your MCP Server”Your MCP server needs to validate whether all the incoming requests have a valid access token. Below is a sample middleware implementation if you are using expressjs for implementing your MCP Server.
You can use Scalekit SDKs (across Node.js, Java, GoLang, Python) to determine whether the token is valid or not and whether the token has appropriate claim values like aud
, iss
, exp
, iat
, scope
etc.
10 collapsed lines
import { Scalekit, TokenValidationOptions } from '@scalekit-sdk/node';import { NextFunction, Request, Response } from 'express';
// Initialize Scalekit client with environment credentialsconst scalekit = new Scalekit( '<SCALEKIT_ENVIRONMENT_URL>', '<SCALEKIT_CLIENT_ID>', '<SCALEKIT_CLIENT_SECRET>');
// Resource configurationconst RESOURCE_ID = 'https://your-mcp-server.com';const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource';
// WWW-Authenticate header for unauthorized responsesexport const WWWHeader = { HeaderKey: 'WWW-Authenticate', HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"`};
export async function authMiddleware(req: Request, res: Response, next: NextFunction) { try { // Allow public access to well-known endpoints for metadata discovery if (req.path.includes('.well-known')) { return next(); }
// Extract Bearer token from Authorization header const authHeader = req.headers['authorization']; const token = authHeader?.startsWith('Bearer ') ? authHeader.split('Bearer ')[1]?.trim() : null;
if (!token) { throw new Error('Missing or invalid Bearer token'); }
// Validate token against configured resource audience await scalekit.validateToken(token, { audience: [RESOURCE_ID] });
next(); } catch (err) { return res .status(401) .set(WWWHeader.HeaderKey, WWWHeader.HeaderValue) .end(); }}
// Apply authentication middleware to all MCP endpointsapp.use('/', authMiddleware);
10 collapsed lines
from scalekit import ScalekitClientfrom scalekit.common.scalekit import TokenValidationOptions
# Initialize Scalekit client with your environment credentialsscalekit_client = ScalekitClient( '<SCALEKIT_ENVIRONMENT_URL>', '<SCALEKIT_CLIENT_ID>', '<SCALEKIT_CLIENT_SECRET>')
# Method 1: validate_access_token - Returns boolean (True/False)# Use this method when you only need to verify token validity without detailed error information.# This approach is suitable for simple authorization checks where you don't need token claims.def validate_token_with_issuer_audience(token: str) -> bool: """ Validates a token and returns True if valid, False otherwise.
:param token: The token to validate :return: True if token is valid, False otherwise """ options = TokenValidationOptions( issuer="<SCALEKIT_ENVIRONMENT_URL>", audience=["your-api-audience"] )
try: is_valid = scalekit_client.validate_access_token(token, options=options) return is_valid except Exception as ex: print(f"Token validation failed: {ex}") return False
# Method 2: validate_token - Returns token claims/payload# Use this method when you need access to token claims (user info, scopes, etc.) or detailed error information.# This approach is suitable for authorization that requires specific user context or scope validation.def validate_token_and_get_claims(token: str) -> dict: """ Validates a token with specific audience and raises exception on failure.
:param token: The token to validate :raises: ScalekitValidateTokenFailureException if validation fails """ options = TokenValidationOptions( issuer="<SCALEKIT_ENVIRONMENT_URL>", audience=["your-api-audience"], required_scopes=["read:users", "write:users"] # Optional: validate specific scopes for finer access control )
scalekit_client.validate_token(token, options=options)
Step 4: (Optional) Implement Scope-based authorization at the tool execution level
Section titled “Step 4: (Optional) Implement Scope-based authorization at the tool execution level”Implement scope validation at the MCP tool execution level to ensure that the tool is only executed if the user has authorized the MCP client for the required scope.
// Validate token has required scope for this specific tool executiontry{ await scalekit.validateToken( token, { audience: [RESOURCE_ID], requiredScopes: [scope] } );} catch(error){ return res.status(403).json({ error: 'insufficient_scope', error_description: `Required scope: ${scope}`, scope: scope });}
# Validate token has required scope for this specific tool executiontry: scalekit_client.validate_access_token( token, options=TokenValidationOptions( audience=[RESOURCE_ID], required_scopes=[scope] ) )except ScalekitValidateTokenFailureException as ex: return { "error": "insufficient_scope", "error_description": f"Required scope: {scope}", "scope": scope }
Next Steps
Section titled “Next Steps”Download our sample MCP Server: We have put together a simple MCP server that you can check out and run it locally to test the end to end functionality of a working MCP server complete with authentication and authorization. You can download and execute a sample MCP server implementation from GitHub.