How to use AuthKit as the authorization server for your MCP server.
Model Context Protocol (MCP) is a new protocol that standardizes how LLM-based clients can programmatically interact with applications. This includes querying data, in the form of resources, or taking direct actions in the application in the form of tools.
This guide is intended for application developers implementing an MCP server that requires authentication. WorkOS and AuthKit can provide a secure way to manage access to your MCP server with minimal effort.
Support for the MCP authorization spec is currently in feature preview. Reach out to WorkOS support if you want early access.
The MCP specification builds on industry-standard protocols like OAuth 2.0 in order to secure access to an MCP server. It makes the following distinctions between entities in the authorization flow:
Support for MCP authorization is built on top of WorkOS Connect, which provides all of the necessary OAuth API endpoints MCP clients will use to authenticate. You can view your AuthKit metadata by making a request to its /.well-known/oauth-authorization-server
endpoint:
curl https://<subdomain>.authkit.app/.well-known/oauth-authorization-server | jq { "authorization_endpoint": "https://<subdomain>.authkit.app/oauth2/authorize", "code_challenge_methods_supported": ["S256"], "grant_types_supported": ["authorization_code", "refresh_token"], "introspection_endpoint": "https://<subdomain>.authkit.app/oauth2/introspection", "issuer": "https://<subdomain>.authkit.app", "registration_endpoint": "https://<subdomain>.authkit.app/oauth2/register", "scopes_supported": ["email", "offline_access", "openid", "profile"], "response_modes_supported": ["query"], "response_types_supported": ["code"], "token_endpoint": "https://<subdomain>.authkit.app/oauth2/token", "token_endpoint_auth_methods_supported": [ "none", "client_secret_post", "client_secret_basic" ] }
AuthKit handles the authentication flow so your MCP server only needs to implement the following concerns:
Your app needs to gate access to the MCP endpoints by verifying access tokens issued by AuthKit for your MCP server. This process is very similar to the way any Connect JWT is verified, with one important addition:
import { jwtVerify, createRemoteJWKSet } from 'jose'; const JWKS = createRemoteJWKSet(new URL('https://<subdomain>.authkit.app/oauth2/jwks')); const WWW_AUTHENTICATE_HEADER = [ 'Bearer error="unauthorized"', 'error_description="Authorization needed"', `resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"`, ].join(', '); const bearerTokenMiddleware = async (req, res, next) => { const token = req.headers.authorization?.match(/^Bearer (.+)$/)?.[1]; if (!token) { return res .set('WWW-Authenticate', WWW_AUTHENTICATE_HEADER) .status(401) .json({ error: 'No token provided.' }); } try { const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://<subdomain>.authkit.app', }); // Use access token claims to populate request context. // i.e. `req.userId = payload.sub;` next(); } catch (err) { return res .set('WWW-Authenticate', WWW_AUTHENTICATE_HEADER) .status(401) .json({ error: 'Invalid bearer token.' }); } };
Note the addition of a WWW-Authenticate
header with the resource_metadata
challenge parameter containing a /.well-known/oauth-protected-resource
URL. This allows clients to dynamically discover the appropriate authorization server, enabling zero-config interoperability between different MCP clients and servers.
Your MCP server should implement /.well-known/oauth-protected-resource
endpoint mentioned in the previous section, returning the following minimal JSON response:
app.get('/.well-known/oauth-protected-resource', (req, res) => res.json({ resource: `https://mcp.example.com`, authorization_servers: ['https://<subdomain>.authkit.app'], bearer_methods_supported: ['header'], }), );
MCP clients that support metadata discovery will automatically fetch this metadata when they initially encounter a 401 Unauthorized
error from the middleware implemented above. Since AuthKit is included in the metadata under authorization_servers
the MCP client will redirect the user to AuthKit in order for them to sign in.
Behind the scenes, AuthKit implements the necessary authorization, dynamic client registration, and token endpoints so that your application doesn’t need to. You can read more in the latest version of the MCP authorization spec but most apps can consider them implementation details of AuthKit as the authorization server.
Upon successful authentication the client will receive credentials and can start making requests to your application’s MCP endpoints.
The MCP space is rapidly evolving and not every client may support the latest version of the specification.
In particular, some clients may not support OAuth 2.0 Protected Resource Metadata and its /.well-known/oauth-protected-resource
endpoint, instead attempting to fetch OAuth 2.0 Authorization Server Metadata directly from your application’s MCP server.
For these clients, your server can implement a metadata endpoint as a proxy with AuthKit as the upstream source of truth:
app.get('/.well-known/oauth-authorization-server', async (req, res) => { const response = await fetch( 'https://<subdomain>.authkit.app/.well-known/oauth-authorization-server', ); const metadata = await response.json(); res.json(metadata); });
Clients will use AuthKit as the authorization server and the rest of the flow will be identical.