Mastering Authentication in MCP: An AI Engineer’s Comprehensive Guide
Last Updated on February 9, 2026 by Editorial Team
Author(s): Neel Shah
Originally published on Towards AI.

As an AI engineer working with the Message Control Protocol (MCP), I’ve implemented and evaluated three authentication methods to secure client-server communication: API Key-based, JWT-based with custom implementation, and JWT-based with FastMCP’s built-in authentication. Each method addresses different needs, from simplicity to enterprise-grade security with role-based access control (RBAC). In this blog, I’ll provide an in-depth exploration of these approaches, including technical details, code snippets, use cases, and best practices, drawing from my hands-on experience building and testing these systems.
Understanding Authentication in MCP
The Message Control Protocol (MCP) enables real-time, tool-based interactions between AI agents (MCP clients) and servers, often using Server-Sent Events (SSE) for communication. Authentication ensures that only authorized clients can access tools like TimeTool or weather_tool. The three methods discussed—API Key-based, custom JWT, and FastMCP’s JWT—offer varying levels of security and complexity, catering to different application requirements.
1. API Key-Based Authentication
Overview
API key-based authentication uses a static key in the request headers, offering simplicity but limited security. It’s ideal for quick prototypes or internal tools where ease of implementation is prioritized.
How It Works
- Client Side: The client sends an
x-api-keyheader (e.g.,secretkey) with SSE requests to the server (e.g.,http://localhost:8100/sse). The client lists and invokes tools likeweather_toolfor queries like “What’s the weather in Dubai?” - Server Side: The server validates the
x-api-keyheader in acheck_authfunction, returning a 401 Unauthorized error if invalid. It also supports Basic Auth and Bearer Token for compatibility. - Implementation Details: Built with FastAPI and Starlette, the server routes requests to tools after validating the key. Tools like
TimeToolandweather_toolfetch time or weather data from external APIs like OpenWeatherMap.
Code Snippet
# Client: Sending API key
headers = {"x-api-key": "secretkey"}
async with sse_client(url="http://localhost:8100/sse", headers=headers) as (in_stream, out_stream):
async with ClientSession(in_stream, out_stream) as session:
info = await session.initialize()
tools = await session.list_tools()
# Server: Validating API key
def check_auth(request: Request):
api_key = request.headers.get("x-api-key")
if api_key == "secretkey":
return True
raise HTTPException(status_code=401, detail="Unauthorized")
Pros
- Simplicity: Minimal setup for rapid development.
- Low Overhead: No token management or cryptographic operations.
- Compatibility: Works with any HTTP client.
Cons
- Security Risks: Static keys are vulnerable to leakage (e.g., in logs or client code).
- No Granularity: Lacks role-based access control (RBAC).
- Scalability Issues: Managing multiple keys is cumbersome for large systems.
Use Case
Best for internal tools or low-security prototypes, such as a simple weather query service or time lookup tool.
2. JWT-Based Authentication with Custom Implementation
Overview
This method uses JSON Web Tokens (JWT) with a custom token issuance endpoint, providing time-limited, signed tokens for improved security over API keys. It balances security and implementation complexity.
How It Works
- Client Side: The client sends a
POSTrequest tohttp://localhost:8100/tokenwith aclient_id(e.g.,test_client) andclient_secret(e.g.,secret_1234). The returned JWT is included as a Bearer token in theAuthorizationheader for SSE requests. - Server Side: The server validates credentials against a mock client store (
CLIENTSdictionary), issues a JWT with a 60-minute expiration, and verifies tokens using the HS256 algorithm and a secret key (my_super_secret_key). - Implementation Details: The client queries tools like
weather_toolto fetch data, and the server enforces token validity, ensuring only authorized clients access tools.
Code Snippet
# Client: Fetching and using JWT
async def get_token():
payload = {"client_id": "test_client", "client_secret": "secret_1234"}
async with aiohttp.ClientSession() as session:
async with session.post("http://localhost:8100/token", json=payload) as resp:
data = await resp.json()
return data["access_token"]
headers = {"Authorization": f"Bearer {await get_token()}"}
async with sse_client(url="http://localhost:8100/sse", headers=headers) as (in_stream, out_stream):
async with ClientSession(in_stream, out_stream) as session:
info = await session.initialize()
# Server: Generating and validating JWT
@app.post("/token")
def generate_token(request: TokenRequest):
if request.client_id in CLIENTS and CLIENTS[request.client_id] == request.client_secret:
payload = {
"sub": request.client_id,
"exp": datetime.datetime.now() + datetime.timedelta(minutes=60)
}
token = jwt.encode(payload, "my_super_secret_key", algorithm="HS256")
return {"access_token": token}
def check_auth(request: Request):
auth = request.headers.get("authorization", "")
if auth.startswith("Bearer "):
token = auth.split(" ", 1)[1]
try:
payload = jwt.decode(token, "my_super_secret_key", algorithms=["HS256"])
return True
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
Pros
- Improved Security: Expiring tokens reduce long-term exposure compared to static keys.
- Customizable: Payloads can include user-specific data like client IDs or roles.
- Scalable: Centralized token issuance simplifies client authentication for larger systems.
Cons
- Complexity: Requires a token endpoint and client store management.
- Overhead: Token encoding and decoding add slight latency.
- Limited RBAC: Offers basic access control compared to advanced frameworks.
Use Case
Suitable for secure APIs with moderate complexity, such as authenticated weather or time services, where time-limited access is needed.
3. JWT-Based Authentication with FastMCP’s Built-In Auth
Overview
FastMCP’s BearerAuthProvider leverages RSA-signed JWTs with role-based access control (RBAC), making it ideal for enterprise applications requiring fine-grained permissions and robust security.
How It Works
- Key Generation: A script (
generate_key.py) creates RSA public/private key pairs, stored inmcp_auth/public.pemandmcp_auth/private.pem. - Client Side: The client signs a JWT with the private key, including claims like
subject(e.g.,alice),issuer,audience, andjob_role(e.g.,Manager). The token is sent in theAuthorizationheader for SSE requests to servers likehttp://localhost:8001/sse. - Server Side: The server validates JWTs using the public key and enforces RBAC, allowing Managers access to
approval,inventory, andorderservers, while Officers are limited toorder. Tools likecreate_project_taskandget_project_tasksare restricted based on roles. - Implementation Details: The
MCPServerAdaptermanages multi-server connections, and safety protocols prompt for user confirmation before data modifications (e.g., creating tasks).
Code Snippet
# Client: Generating RSA-signed JWT
with open("mcp_auth/private.pem", "r") as f:
private_key_pem = f.read()
key_pair = RSAKeyPair(private_key=SecretStr(private_key_pem), public_key=public_key_pem)
token = key_pair.create_token(subject="alice", issuer="https://dev-issuer.com", audience="my-mcp-server", additional_claims={"job_role": "Manager", "id": "123", "name": "Alice"})
headers = {"Authorization": f"Bearer {token}"}
claims = jwt.decode(
token,
public_key_pem,
algorithms=["RS256"],
audience="my-mcp-server",
issuer="https://dev-issuer.com"
)
print('------------------------------')
print(claims)
print('--------------------------------')
role = claims.get("job_role", "")
print(f"Authenticated as {claims['name']} with role {role} with id {claims['id']}")
# Setup allowed servers by role
if role == "Manager":
servers = [
{"url": "http://localhost:8001/sse", "transport": "sse", "headers": headers}, # HR Management
{"url": "http://localhost:8002/sse", "transport": "sse", "headers": headers}, # Project Management
{"url": "http://localhost:8003/sse", "transport": "sse", "headers": headers}, # CRM
]
permitted_server = ['hr_management', 'project_management', 'crm']
non_permitted_server = ['NA']
elif role == "AssistantManager":
servers = [
{"url": "http://localhost:8001/sse", "transport": "sse", "headers": headers}, # HR Management
{"url": "http://localhost:8002/sse", "transport": "sse", "headers": headers}, # Project Management
]
permitted_server = ['hr_management', 'project_management']
non_permitted_server = ['crm']
else:
servers = [{"url": "http://localhost:8001/sse", "transport": "sse", "headers": headers}] # HR Management only
permitted_server = ['hr_management']
non_permitted_server = ['project_management', 'crm']
# Server: Configuring FastMCP
with open("mcp_auth/public.pem", "r") as f:
public_key = f.read()
auth = BearerAuthProvider(public_key=public_key, issuer="https://dev-issuer.com", audience="my-mcp-server")
mcp = FastMCP(name="ProjectManagementMCP", auth=auth)
Pros
- Robust Security: RSA signatures provide strong cryptographic guarantees.
- RBAC Support: Fine-grained access control for complex systems.
- Scalability: Supports multi-server setups with built-in safety prompts for data modifications.
- Enterprise-Ready: Designed for production-grade applications.
Cons
- Complexity: Requires key pair management and RBAC configuration.
- Setup Overhead: Generating and distributing RSA keys adds initial effort.
- Performance Cost: RSA operations are slower than HS256 or API key checks.
Use Case
Ideal for enterprise systems like project management platforms, where different roles (e.g., Manager, AssistantManager) need specific access to tools and servers.
Comparison of Authentication Methods

Aspect API Key-Based JWT Custom JWT FastMCP Security Low (static key) Medium (expiring tokens) High (RSA, RBAC) Complexity Low Medium High Scalability Limited Good Excellent Access Control None Basic Advanced (RBAC) Use Case Prototypes Secure APIs Enterprise systems
Practical Considerations for AI Engineers
Security Best Practices
- Secure Storage: Store API keys, JWT secrets, and RSA keys in environment variables or a secure vault (e.g., AWS Secrets Manager).
- Token Expiration: Use short-lived tokens (e.g., 60 minutes) to limit exposure, with refresh mechanisms for JWTs.
- HTTPS: Enforce HTTPS for all MCP communication to prevent interception of keys or tokens.
- Logging: Avoid logging sensitive data; use structured logging for audit trails.
Scalability Tips
- Centralized Auth: For JWT-based methods, use a dedicated token issuance service to streamline client authentication.
- Caching: Cache RSA public keys or token validation results to reduce latency.
- Rate Limiting: Apply rate limits to authentication endpoints to prevent abuse.
Implementation Challenges
- API Key: Key rotation and revocation are difficult in large systems.
- JWT Custom: Requires robust token refresh logic and client store management.
- FastMCP JWT: Complex key management and RBAC policy design can be time-intensive.
Choosing the Right Method
- API Key-Based: Best for rapid prototyping or internal tools with minimal security needs.
- JWT Custom: Suitable for secure APIs with moderate complexity, like authenticated weather services.
- JWT FastMCP: Ideal for enterprise applications requiring RBAC, such as project management systems.
Reference Code
Github link: https://github.com/NeelDevenShah/MCP-Auth-Demo
Conclusion
Selecting the right authentication method for MCP depends on your application’s requirements for security, scalability, and complexity. API key-based authentication offers simplicity but lacks robustness, making it suitable for prototypes. Custom JWT implementation provides a balance of security and flexibility for secure APIs. FastMCP’s RSA-based JWT with RBAC is the gold standard for enterprise-grade systems needing fine-grained access control. By understanding the trade-offs and applying best practices, you can build secure, scalable MCP applications tailored to your project’s needs, from simple tools to complex AI-driven platforms.
Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.
Published via Towards AI
Towards AI Academy
We Build Enterprise-Grade AI. We'll Teach You to Master It Too.
15 engineers. 100,000+ students. Towards AI Academy teaches what actually survives production.
Start free — no commitment:
→ 6-Day Agentic AI Engineering Email Guide — one practical lesson per day
→ Agents Architecture Cheatsheet — 3 years of architecture decisions in 6 pages
Our courses:
→ AI Engineering Certification — 90+ lessons from project selection to deployed product. The most comprehensive practical LLM course out there.
→ Agent Engineering Course — Hands on with production agent architectures, memory, routing, and eval frameworks — built from real enterprise engagements.
→ AI for Work — Understand, evaluate, and apply AI for complex work tasks.
Note: Article content contains the views of the contributing authors and not Towards AI.