Authentication flow
MCP authentication is built on OAuth 2.1 and specifically uses bearer tokens obtained through OAuth authorization flows. This ensures that AI agents can securely authenticate on behalf of users to access your protected services.
The MCP authentication flow
Section titled “The MCP authentication flow”The MCP authentication flow involves several steps and multiple parties. We’ll step through an example to show how it works.
In these examples, the MCP server is hosted at https://example.com/mcp.
From the point-of-view of the MCP server:
- the MCP client issues a request without a bearer token
- the MCP server rejects this request, and tells the MCP client where it needs to go to get resource metadata (i.e. the info it needs to perform auth)
- the MCP client requests resource metadata from the MCP server
- Magic happens (the MCP client uses the metadata to complete an auth flow with the user, and obtains a bearer token)
- the MCP client issues a request with a bearer token
- the MCP server checks the bearer token and responds to the request
Step 1: Initial request and 401 response
Section titled “Step 1: Initial request and 401 response”When an MCP client makes a request to your server without authentication, your server responds with a 401 Unauthorized status. This response includes a WWW-Authenticate header that points to a resource metadata document.
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource/mcp"The resource metadata URL in the response can be anything, but for compatibility, it should follow the structure above; basically
/.well-known/oauth-protected-resource has been inserted at the start of the path (i.e. before /mcp). This is the same URL
that will be used if you don’t supply resource_metadata in your response; it’s best to keep everything aligned though.
Step 2: Resource metadata discovery
Section titled “Step 2: Resource metadata discovery”The MCP client fetches the resource metadata from the URL specified. If you’re following the pattern outlined above, this document will need to be hosted on your MCP server. This metadata contains information about the authorization server and the resource to ask for access to (the URL of your MCP server).
{ "authorization_servers": ["https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz"], "resource": "https://example.com/mcp"}Technically you can specify multiple authorization servers, but this isn’t necessary with Prefactor, so we won’t cover it here.
Note also that you don’t specify the discovery URL of the authorization server, you specify the base path. With Prefactor there will always be a path component like shown above corresponding to the pool you have set up, even if there is a custom domain.
This is the last interaction the MCP client will have with your MCP server until the auth process is complete.
Step 3: Authorization server metadata discovery
Section titled “Step 3: Authorization server metadata discovery”Now that the MCP client knows what authorization server you are using, it can fetch the discovery metadata to understand the rest of the configuration.
In our example, the URL fetched will be https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/.well-known/oauth-authorization-server.
The document returned will look something like:
{ "authorization_endpoint": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/authorize", "code_challenge_methods_supported": [ "S256" ], "end_session_endpoint": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/end_session", "grant_types_supported": [ "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange" ], "introspection_endpoint": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/introspect", "issuer": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz", "jwks_uri": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/jwks", "registration_endpoint": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/register", "response_types_supported": [ "code" ], "scopes_supported": [], "subject_types_supported": [ "public" ], "token_endpoint": "https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/token"}The details here may vary between authorization servers or change, which is why MCP clients use the metadata endpoint.
Step 4: Dynamic client registration
Section titled “Step 4: Dynamic client registration”The MCP client registers itself as a new OAuth client with your authorization server. It uses the URL in the registration_endpoint field
of the authorization server metadata retrieved in the previous step.
This gives the client the credentials it needs to start the authorization process.
POST https://auth.prefactor.net/mcp.0123456789abcdefghjkmnpqrstvwxyz/register HTTP/1.1Content-Type: application/json
{ "client_name": "Claude", "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "client_secret_basic"}Note that the redirect URI is going to send the auth process back to the client, not your MCP server. This is normal and part of the way that MCP auth differs from how OAuth is generally implemented in web apps. (The parallel to web apps would be if the browser actually knew what OAuth was, and did all the auth separately from your app.)
The authorization server responds with the details the MCP client can use in future requests:
{ "client_id": "d.0123456789abcdefghjkmnpqrstvwzzz", "client_secret": "YOUR_SECRET_HERE", "client_id_issued_at": 2893256800, "client_secret_expires_at": 2893276800, "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"], "grant_types": ["authorization_code", "refresh_token"], "client_name": "Claude", "token_endpoint_auth_method": "client_secret_basic"}The client ID and secret returned here identify the agent, but not a user. So there is still another step to perform: get the user to authenticate.
Step 5: Standard(ish) OAuth flow
Section titled “Step 5: Standard(ish) OAuth flow”We’ll gloss over this a little bit as it’s essentially a standard OAuth flow, but one that the MCP host starts directly. Your MCP server will not see any of the interactions; so everything the MCP client needs to tell the authorization server must be established in the previous steps.
The only interesting parts to note here are:
- the callback URL will (as noted above) send the user back to the MCP client, not your app, once authentication is complete.
- the MCP client holds the access token issued, and is in charge of refreshing it when required.
Step 5: Reissue the original request, this time with a bearer token
Section titled “Step 5: Reissue the original request, this time with a bearer token”Once the client has an access token, it can make authenticated requests to your MCP server:
POST /mcp HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Content-Type: application/json
{ "id": 1, "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "get_user_data", "arguments": { "user_id": "12345" } }}It’s up to the MCP server to validate the token. It needs to make sure that:
- it was issued by the authorization server
- it is current
- it was issued for the right MCP server
- that it has the correct scopes
Prefactor has APIs and techniques which can simplify this task.