A developer’s guide to MCP auth
Everything you need to know to secure your MCP server using OAuth 2.1 and PKCE, server and auth metadata, client registration, JWT validation, and role-based access control.
As AI agents grow more capable every day and can access more systems with the help of MCP servers, the question of security becomes crucial. These agents can move money, change account state, approve refunds, or deploy builds. You need to know who is calling and what they are authorized to do, and you need a fast way to turn access off when things change. Authentication and authorization move from a checklist to the core of the design.
This guide explains how MCP handles authentication and authorization in detail. It starts by setting the stage with the overall structure of MCP and the role of auth inside it. From there, it explores how to secure an MCP server with OAuth 2.1 and PKCE, discovery using Protected Resource Metadata (RFC 9728) and Authorization Server Metadata (RFC 8414), Dynamic Client Registration, correct JWT validation, and RBAC enforcement. You will learn how to design least privilege scopes, validate tokens correctly, and ship auditable MCP integrations that scale.
By the end, you will understand not just how to configure OAuth for MCP, but how to build systems that are both usable and secure.
MCP architecture 101
To understand auth in MCP, let’s start with how it organizes communication.
There are three parties: the host, the client, and the server.
- The host is an AI-powered application (like Claude Desktop or Cursor) with which the end-user interacts. A host runs one or more MCP clients.
- The client handles the MCP protocol on the app’s side and maintains a dedicated connection to a server. Each client connects directly to a single MCP server.
- The server is where logic lives and actions happen. It performs computations, fetches data, or triggers operations that affect the outside world.

Every request crossing the boundary between client and server must be authenticated and authorized.
- Authentication answers: “Who is making this request?”
- Authorization answers: “What can they do once they are identified?”
This one-to-one connection between client and server keeps the design simple. Each server can enforce its own policies and token validation without worrying about multiple clients sharing a session or identity. It also allows precise control over configuration, state, and logging.
!!For more on the internal workings of MCP servers, see How MCP servers work: Components, logic, and architecture.!!
The evolution of MCP auth
In early versions, MCP focused on connection structure, like defining how servers expose tools and handle prompts. But as developers started using MCP to bridge AI systems with production APIs, one challenge became impossible to ignore: secure identity and access control.
That is why MCP now relies on OAuth 2.1, the same protocol that already secures much of the modern web, from login flows to API tokens. MCP builds on top of OAuth, so developers can use familiar patterns instead of inventing new ones. It defines how clients discover authorization servers, request tokens, and validate permissions. The result is a consistent, secure, and auditable system that works across different environments.
This shift also gives developers flexibility. You can authenticate using enterprise SSO through a provider like WorkOS, use static API keys for system-level access, or delegate to a third-party authorization server if needed. MCP provides a unified way to handle all these cases while maintaining strong security boundaries.
Authentication patterns in MCP
Every MCP server needs a way to authenticate calls, but not all use cases are the same. In practice, you’ll see two main approaches: API keys and OAuth.
1. Service access via API keys
Many MCP servers start with a simple API key for authentication. This method is straightforward and works well for local setups, quick prototypes, or demos. However, in production environments, it introduces significant risks:
- Unlimited access: Anyone who obtains the key can use every feature the server provides. There is no way to restrict access to specific actions or resources.
- Hard to rotate: If the key is leaked, it must be replaced everywhere it’s used, which can break integrations and cause downtime.
- No expiry or traceability: API keys typically never expire and cannot be tied to individual users or devices, which makes auditing and incident response difficult.
API keys remain useful for simple service-to-service connections or local development. But they should be treated as temporary measures, not long-term solutions.
2. User or service access via OAuth
For most production scenarios, OAuth 2.1 is the recommended standard for MCP authentication. OAuth replaces static keys with scoped, time-limited tokens that are issued by a trusted authorization server.
- Each client or user is granted only the specific permissions (scopes) they need.
- Tokens expire automatically and can be revoked without affecting other clients.
- Activities are auditable, since each token is linked to a distinct client identity.
When no user is present and machines communicate directly, the OAuth client credentials flow provides secure, revocable tokens for service access.
When a user is involved, the authorization code flow with PKCE or device flow is preferred.
This model provides stronger guarantees and scales better for enterprise deployments. It ensures that MCP servers can enforce who is calling, what they can access, and how long that access remains valid.
The core OAuth flow for MCP
At the heart of MCP authorization is the standard OAuth 2.1 flow. In a typical setup, three actors are involved:
- MCP client: a local LLM app such as Claude Desktop.
- OAuth server: an auth provider like WorkOS.
- MCP server: the server doing the job you want, like GitHub (for working on issues) or Playwright (for testing UI features).
Here’s what happens step by step:
- The user uses the MCP client to access a feature that requires communication with an MCP server (e.g., create a GitHub issue).
- The client opens a browser and redirects the user to the OAuth server (e.g., WorkOS).
- The user logs in and grants permission for the client to access their account.
- The OAuth server returns an authorization code to the client.
- The client exchanges this code for an access token.
- The client uses the access token to call the MCP server.

This token represents delegated, time-limited permission to perform specific actions on behalf of the user.
Unlike an API key, which gives full, permanent access, an OAuth token can expire, be revoked, and be scoped to specific permissions such as read:messages or write:files.
Handle public clients with PKCE
The flow we just saw is called Authorization Code Flow and it’s a little more complicated than we let on. For example, in the 5th step, when the client exchanges the authorization code for an access token, the request also includes a secret password, known as the client secret. This is known only to the OAuth server and the client making the request, and it’s used to convince the server that the client is who it claims to be. Otherwise, anyone can intercept the authorization code and claim to be the client in order to get an access token.
This works fine for confidential clients, i.e., apps that run on servers and can safely store secrets. However, most MCP clients are public clients, which means they cannot safely store secrets. So they cannot be trusted with a client secret because anything shipped with the MCP client could be extracted.
PKCE (Proof Key for Code Exchange, RFC 7636) solves this problem elegantly.
When starting the authorization flow, the client generates a code verifier, a random string used only once, and hashes it to produce a code challenge.
The challenge is sent in the initial authorization request. Later, when exchanging the authorization code for an access token, the client must present the original verifier.
.webp)
If an attacker intercepts the authorization code in transit, they will not have the verifier needed to complete the exchange.
In practice, this means that MCP clients can authenticate securely without ever having to embed a client secret.
PKCE was originally designed for mobile apps, but it has become the standard for any public client, including those in MCP. And now with OAuth 2.1, it’s required for all clients, both public and confidential. It ensures that the entire flow can happen in the open without secrets that could be leaked.
Protected Resource Metadata: The MCP server’s security guide
Once a client knows the MCP server’s URL, it still needs to understand how to interact securely:
- What token formats does the server accept?
- Which authorization servers does it trust?
- Which scopes are available?
Instead of hardcoding this configuration, the server publishes a machine-readable document at a well-known URL:
This is defined in RFC 9728, and it’s basically a discovery document.
When a client connects without credentials, the server should respond with a 401 Unauthorized and a WWW-Authenticate header pointing to that URL.
Example:
This metadata provides the client with everything it needs to know: the server’s identity, trusted authorization servers, supported bearer methods, and where to find the signing keys.
What used to require manual configuration can now be discovered and automated.
Authorization Server Metadata: The OAuth server’s communication manual
Once the client knows which authorization server to use, it needs to understand how to communicate with it.
RFC 8414, defines how authorization servers can publish their capabilities via their own well-known endpoint:
This document lists all the information needed to complete the flow: the login URL, token endpoint, supported scopes, grant types, and PKCE methods.
Example:
This means clients no longer need hand-written configurations for each provider.
They can follow the metadata pointers and adapt automatically to new environments, which is critical in MCP where users may connect to dozens of services.
Importantly, the OAuth server and the MCP server can be separate. You can use an existing identity provider like WorkOS or Okta to handle login while your MCP server focuses on domain logic.
Dynamic Client Registration: How clients introduce themselves
The last piece of the puzzle is registration.
Traditionally, OAuth clients had to be registered manually with each authorization server, which doesn’t scale when new MCP servers and clients can appear at any time.
RFC 7591 solves this by allowing clients to register themselves dynamically.
Instead of an admin manually creating entries, the client sends a POST request to the registration_endpoint with its metadata, supported grant types, and redirect URIs.
Example (simplified):
The OAuth server responds with a new client_id (and optionally a secret, if appropriate).
This makes MCP ecosystems truly self-serve: any compatible client can connect to any compatible server, without manual setup or approval steps.
Access Tokens: Validate before use
Once an MCP client obtains an access token, it includes it as a bearer token in every request to the MCP server. The server must then verify that the token is valid and trusted before performing any action.
Most OAuth tokens are JWTs (JSON Web Tokens), which carry signed claims that describe the token’s origin and permissions.
A JWT consists of 3 parts:
- Header: Specifies the token type (“JWT”) and the signing algorithm used to create the token’s signature (like HMAC SHA256 or RSA).
- Payload: Contains the claims: pieces of information about the user. There are standard claims (which are registered with the Internet Assigned Numbers Authority (IANA) and defined by the JWT specification) and custom ones (which are used to represent the information we want our JWT to contain, e.g., the user’s email or profile picture). Some examples are:
- sub: Standard claim that specifies the subject of the JWT, for example, a user ID.
- iss: Standard claim that specifies the issuer of the JWT.
- aud: Standard claim that specifies the recipient for which the JWT is intended.
- exp: Standard claim that specifies the time after which the JWT expires.
- email: A custom claim that contains the user’s email.
- Signature: A signature is used to verify that the sender of the JWT is who they say they are and to ensure that the message wasn't tampered with along the way.
To validate a JWT, the MCP server typically performs the following steps:
After the MCP server has parsed the JWT to extract the header, payload, and signature, it should follow these steps to validate the contents:
- Signature: Verify the token using the public key (available from the
jwks_uri
in metadata). This ensures the token was issued by a trusted authorization server. - Expiry: Ensure the token has not expired. Check the expiration time (
exp
) and the not-before time (nbf
) claims. - Issuer (
iss
): Confirm the token’siss
claim matches the expected authorization server URL. - Audience (
aud
): Check that the token is meant for your MCP server’s resource identifier. - Scope: Confirm the token includes the scope required for the requested action (e.g.,
read_report
).
Example in Python using PyJWT:
!!For more information on validating JWTs and popular libraries you can use for other programming languages, see JWT validation: how-to and best libraries to use.!!
By verifying each of these fields, the server can confidently determine who issued the token, for what purpose, and for how long it remains valid.
This process is crucial because MCP often connects sensitive data sources. A single invalid or misvalidated token could allow unauthorized access across multiple services.
Enforcing access control with RBAC
Token validation proves that a request is authentic. For example, the client X has been authorized by the user to read financial information. But is the user actually allowed to do that?
The most common strategy to enforce access control is Role-Based Access Control (RBAC), where roles map to sets of permissions that correspond to OAuth scopes.
For example:
When the MCP server validates a token, it checks the token’s scopes and matches them against the roles defined in its own policy. This ensures that clients can only perform actions that correspond to their assigned roles.
The enforcement step is where business logic meets security. Once a token is validated, the MCP server evaluates whether the roles and scopes it carries actually authorize the requested action. This final check ensures that even with valid credentials, clients can only act within their intended boundaries.
Putting it all together
Together, these specifications form the backbone of OAuth in MCP:
- OAuth 2.1 handles token issuance and permission scopes.
- PKCE secures public clients without secrets.
- RFC 9728 standardizes how servers advertise their security configuration.
- RFC 8414 describes how authorization servers expose their capabilities.
- RFC 7591 enables fully dynamic registration at scale.
These specifications form a complete authorization workflow:
- Discovery: The MCP client connects, receives a 401, and retrieves the server’s protected resource metadata.
- Registration: The client registers dynamically with the authorization server if necessary.
- Authorization: The user or service performs an OAuth 2.1 flow, secured with PKCE.
- Token use: The client calls the MCP server using a bearer token. The server validates it based on issuer, audience, scope, and expiry.
- Enforcement: The MCP server enforces scopes and permissions defined by the authorization server or by its own policy.

With these in place, MCP achieves a robust, interoperable, and fully discoverable authorization system that works safely for both users and machines.
Use WorkOS as your OAuth server for MCP
You can wire up everything you just read with WorkOS in two ways. Both follow OAuth 2.1, support PKCE and Dynamic Client Registration, and work across compliant MCP clients.
Option 1: Bring your own users (OAuth bridge)
Use this when you already have an app with its own login and user store. WorkOS Connect acts as an OAuth bridge: MCP clients authenticate through WorkOS, users sign in to your app, and WorkOS issues tokens the MCP client can use with your MCP server. This is how Mux launched its MCP server quickly without rebuilding auth. Read more about this at How WorkOS solved enterprise auth for MCP servers.
- What you implement:
- Protected Resource Metadata on your MCP server
- A 401 with
WWW-Authenticate
pointing to it - JWT validation using WorkOS JWKS
- What WorkOS handles:
- Client authentication flow
- Consent
- Issuing tokens
- Dynamic Client Registration
- Metadata at
/.well-known/oauth-authorization-server
.
For more details, see our docs.
Option 2: Managed users with AuthKit
Don’t want to run user auth? Use AuthKit for your logins. AuthKit hosts the full OAuth flow, clients self-register via Dynamic Client Registration, and your MCP server validates tokens it receives.
- What you implement: the same Protected Resource Metadata and JWT checks; you list AuthKit under
authorization_servers
. - What WorkOS handles: login UI, client and user authentication, consent, issuing tokens, Dynamic Client Registration, introspection, and the AS metadata endpoint.
For more details, see our docs.
!!Want a working example? mcp.shop is a public demo store secured with WorkOS AuthKit and used in live talks and videos. Browse the source, or watch a short end-to-end demo that includes OAuth, PKCE, and token validation.!!
What the integration looks like
- Discovery: Serve
/.well-known/oauth-protected-resource
on your MCP server and include WorkOS/AuthKit underauthorization_servers
. Clients that hit a 401 learn how to start OAuth automatically. - Registration: Enable Dynamic Client Registration in the WorkOS dashboard so MCP clients can self-register.
- Token use: Validate JWTs against the AS
jwks_uri
, checkiss
,aud
,exp
, and required scopes before executing any tool call.
All public MCP clients use PKCE; WorkOS enforces it in supported flows.
Add roles and permissions with WorkOS RBAC
Map OAuth scopes to internal roles and permissions using WorkOS RBAC:
- Define roles like
reader
andeditor
- Assign multiple roles per user
- Use role hierarchies so higher-privilege roles inherit lower-level permissions.
Each access token will include the granted permissions as scopes
. Your MCP server should enforce these permissions after token validation.
For more information on roles and permissions, see our docs.
Final thoughts
OAuth gives you a practical recipe for secure AI flows: discoverability with well-known endpoints, client onboarding with Dynamic Client Registration, OAuth 2.1 with PKCE for public clients, and strict JWT validation at the server.
Whether you bring your own users with WorkOS or use AuthKit to manage them, you can ship an MCP server that is least-privilege by default, auditable, and easy to maintain.
Start small: publish /.well-known/oauth-protected-resource
, enable Dynamic Client Registration, validate tokens against iss
, aud
, and exp
, map scopes to roles, and log decisions. From there, iterate on scopes and roles as real workloads arrive, and you will have an auth foundation that scales with your product, not against it.
Sign up for WorkOS today and add secure authentication to your MCP server.