Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
6ce1157
Add AWS Cognito OAuth provider for FastMCP authentication
stephaneberle9 Sep 19, 2025
a218059
Add a comprehensive test suite for AWS Cognito OAuth provider
stephaneberle9 Sep 19, 2025
9afb431
Add complete documentation for AWS Cognito OAuth authentication in Fa…
stephaneberle9 Sep 19, 2025
d407330
Merge branch 'main' into feat/aws-cognito-auth-provider
stephaneberle9 Sep 19, 2025
c764664
Enhance AWS Cognito OAuth examples with enhanced user profile handling:
stephaneberle9 Sep 19, 2025
a523a96
Adjust versions in docs/integrations/aws-cognito.mdx
stephaneberle9 Sep 22, 2025
71a737c
Fix inconsistent tool name in example code snippet in docs/integratio…
stephaneberle9 Sep 22, 2025
0d5a819
Remove unnecessary hasattr check from client example and docs
stephaneberle9 Sep 22, 2025
dc4ed4a
Have all imports at the top of the file
stephaneberle9 Sep 22, 2025
6a4f2ca
Move more inlined imports to the top of the file
stephaneberle9 Sep 22, 2025
f4310a5
Align AWS Cognito provider with access token standards
stephaneberle9 Sep 22, 2025
c5d9b05
Refactor AWSCognitoTokenVerifier to extend JWTVerifier
stephaneberle9 Sep 22, 2025
593db58
Remove unused timeout_seconds parameter from AWS Cognito provider
stephaneberle9 Sep 22, 2025
f54ac37
Remove documentation of unused timeout_seconds parameter removed from…
stephaneberle9 Sep 22, 2025
385d013
Refactor AWS Cognito provider to use OIDC Discovery and eliminate dom…
stephaneberle9 Sep 22, 2025
121bee5
Update documentation to include AWS in OAuth provider mentions
stephaneberle9 Sep 23, 2025
f7f8faf
Add Keycloak OAuth authentication provider with complete example setup
stephaneberle9 Sep 23, 2025
19c56a8
Implement server-side scope injection and FastMCP compatibility modif…
stephaneberle9 Sep 25, 2025
fb8410a
Configure Keycloak realm for Dynamic Client Registration
stephaneberle9 Sep 25, 2025
4170258
Add comprehensive test suite for Keycloak OAuth authentication provider
stephaneberle9 Sep 25, 2025
8353dbb
Update Keycloak example configuration and realm file for improved
stephaneberle9 Sep 27, 2025
9ac8006
Add fix/workaround for "Client not found" error after
stephaneberle9 Sep 27, 2025
4f5dff5
Add comprehensive Keycloak OAuth integration documentation
stephaneberle9 Sep 27, 2025
e4a0b24
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Sep 27, 2025
14174c1
Fix unit test failures
stephaneberle9 Sep 27, 2025
715aa92
Reorganize Keycloak auth example documentation and setup
stephaneberle9 Oct 6, 2025
1c0121d
Merge remote-tracking branch 'upstream/main' into feat/keycloak-auth-…
stephaneberle9 Oct 6, 2025
986b1f7
Fix KeycloakAuthProvider.get_routes() method signature
stephaneberle9 Oct 6, 2025
58decf3
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Oct 21, 2025
321497d
Fix Keycloak realm import volume path
stephaneberle9 Oct 10, 2025
69c699c
Use correct path-scoped OAuth protected resource endpoint in Keycloak…
stephaneberle9 Oct 21, 2025
8d86a1a
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Oct 21, 2025
edc7407
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Oct 30, 2025
dc4ecae
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Oct 31, 2025
3c8ed2b
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Oct 31, 2025
def60e3
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 2, 2025
eb73267
Fix CodeRabbit review comments for Keycloak OAuth provider
stephaneberle9 Nov 2, 2025
6f5622c
Fix remaining CodeRabbit review issues for Keycloak provider
stephaneberle9 Nov 2, 2025
df735d2
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 3, 2025
ccc8b71
Fix type errors in KeycloakAuthProvider URL handling
stephaneberle9 Nov 3, 2025
67af227
Improve robustness and error handling in KeycloakAuthProvider
stephaneberle9 Nov 3, 2025
57648de
Fix HTTP proxying issues in KeycloakAuthProvider
stephaneberle9 Nov 3, 2025
0c767c7
Enforce provider-level required scopes with custom verifiers
stephaneberle9 Nov 3, 2025
6c55017
Fix proxy robustness issues in KeycloakAuthProvider
stephaneberle9 Nov 3, 2025
e75a34a
Add configurable audience validation to KeycloakAuthProvider
stephaneberle9 Nov 3, 2025
f474f1d
Fix minor issues in Keycloak integration docs and examples
stephaneberle9 Nov 3, 2025
3bcbb36
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 3, 2025
33ac4bd
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 4, 2025
443a12d
feat: Add Docker Compose v1/v2 compatibility to Keycloak startup script
stephaneberle9 Nov 16, 2025
8506312
refactor: Remove manual cache clearing from Keycloak OAuth example
stephaneberle9 Nov 16, 2025
de97d8a
fix: Disable audience validation by default in Keycloak example
stephaneberle9 Nov 16, 2025
2e546e5
feat: Add MCP Inspector compatibility to Keycloak OAuth example
stephaneberle9 Nov 17, 2025
ceeca60
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 17, 2025
3ff1a95
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Nov 20, 2025
f2eda1a
Simplify Keycloak provider to minimal DCR proxy (option 1 from PR #1937)
stephaneberle9 Nov 21, 2025
f44cd00
Add basic client scope to Keycloak realm config
stephaneberle9 Nov 22, 2025
ff0de0c
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Dec 8, 2025
ff8163f
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Jan 13, 2026
d243b39
Fix type checking warnings in Keycloak provider tests
stephaneberle9 Jan 13, 2026
44e6dfd
Remove KeycloakProviderSettings for FastMCP 3.0 compatibility
stephaneberle9 Jan 13, 2026
90638b4
Restructure Keycloak documentation to match other provider docs
stephaneberle9 Jan 13, 2026
9cc1066
Update Keycloak documentation to reference upstream fix for DCR issue
stephaneberle9 Jan 13, 2026
db89f0a
Merge remote-tracking branch 'upstream/main' into feat/keycloak-auth-…
stephaneberle9 Feb 1, 2026
39a1be7
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Feb 20, 2026
cddb066
Merge branch 'main' into feat/keycloak-auth-provider
stephaneberle9 Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@
"integrations/discord",
"integrations/github",
"integrations/google",
"integrations/keycloak",
"integrations/oci",
"integrations/propelauth",
"integrations/scalekit",
Expand Down
335 changes: 335 additions & 0 deletions docs/integrations/keycloak.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
---
title: Keycloak OAuth 🤝 FastMCP
sidebarTitle: Keycloak
description: Secure your FastMCP server with Keycloak OAuth
icon: shield-check
tag: NEW
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.12.4" />

This guide shows you how to secure your FastMCP server using **Keycloak OAuth**. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern with Dynamic Client Registration (DCR), where Keycloak handles user login and your FastMCP server validates the tokens.

<Note>
**MCP Compatibility Note**: Older Keycloak versions (prior to the fix in [PR #45309](https://github.com/keycloak/keycloak/pull/45309), merged January 12, 2026) have a limitation where they ignore the client's requested `token_endpoint_auth_method` and always return `client_secret_basic`, but the MCP specification requires `client_secret_post`. The KeycloakAuthProvider includes a minimal proxy workaround that intercepts DCR responses from Keycloak and fixes this field automatically. All other OAuth functionality (authorization, token issuance, user authentication) is handled directly by Keycloak. Once you upgrade to a Keycloak version that includes the fix, the proxy workaround will no longer have any effect.
</Note>

## Configuration

### Prerequisites

Before you begin, you will need:
1. A **[Keycloak](https://keycloak.org/)** server instance running (can be localhost for development, e.g., `http://localhost:8080`)

<Tip>
To spin up Keycloak instantly on your local machine, use Docker:

```bash
docker run --rm \
--name keycloak-fastmcp \
-p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin123 \
quay.io/keycloak/keycloak:26.3 \
start-dev
```

Then access the admin console at `http://localhost:8080` with username `admin` and password `admin123`.
</Tip>

<Tip>
If you prefer using Docker Compose instead, you may want to have a look at the [`docker-compose.yaml`](https://github.com/jlowin/fastmcp/blob/main/examples/auth/keycloak_auth/keycloak/docker-compose.yml) file included in the Keycloak auth example.
</Tip>

2. Administrative access to create and configure a Keycloak realm
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`)

### Step 1: Configure Keycloak for Dynamic Client Registration (DCR)

<Steps>
<Step title="Prepare the Realm Configuration">
Before importing, you should review and customize the pre-configured realm file:

1. Download the FastMCP Keycloak realm configuration: [`realm-fastmcp.json`](https://github.com/jlowin/fastmcp/blob/main/examples/auth/keycloak_auth/keycloak/realm-fastmcp.json)
2. Open the file in a text editor and customize as needed:
- **Realm name and display name**: Change `"realm": "fastmcp"` and `"displayName": "FastMCP Realm"` to match your project
- **Trusted hosts configuration**: Look for `"trusted-hosts"` section and update IP addresses for secure client registration:
- `localhost`: For local development
- `172.17.0.1`: Docker network gateway IP address (required when Keycloak is run with Docker and MCP server directly on localhost)
- `172.18.0.1`: Docker Compose network gateway IP address (required when Keycloak is run with Docker Compose and MCP server directly on localhost)
- `github.com`: Required for MCP Inspector compatibility
- For production, replace these with your actual domain names
- **Default scopes**: The configuration sets `openid`, `profile`, and `email` as default scopes for all clients
3. **Review the test user**: The file includes a test user (`testuser` with password `password123`). You may want to:
- Change the credentials for security
- Replace with more meaningful user accounts
- Or remove and create users later through the admin interface

<Warning>
**Production Security**: Always review and customize the configuration before importing, especially realm names, trusted hosts, and user credentials.
</Warning>
</Step>

<Step title="Import the Realm Configuration">
<Note>
The following instructions are based on **Keycloak 26.3**. Menu items, tabs, and interface elements may be slightly different in other Keycloak versions, but the core configuration concepts remain the same.
</Note>

1. In the left-side navigation, click **Manage realms** (if not visible, click the hamburger menu (☰) in the top-left corner to expand the navigation)
2. Click **Create realm**
3. In the "Create realm" dialog:
- Drag your `realm-fastmcp.json` file into the **Resource file** box (or use the "Browse" button to find and select it)
- Keycloak will automatically read the realm name (`fastmcp`) from the file
- Click the **Create** button

That's it! This single action will create the `fastmcp` realm and instantly configure everything from the file:
- The realm settings with default scopes for all clients (`openid`, `profile`, `email`)
- The "Trusted Hosts" client registration policy for secure Dynamic Client Registration (DCR)
- The test user with their credentials
</Step>

<Step title="Verify the Configuration">
After import, verify your realm is properly configured:

1. **Check the realm URL**: `http://localhost:8080/realms/fastmcp`
2. **Verify DCR policies**: Navigate to **Clients** → **Client registration** to see the imported `"Trusted Hosts"` policy with the trusted hosts you have configured earlier
3. **Test user access**: The imported test user can be used for initial testing

<Note>
Your realm is now ready for FastMCP integration with Dynamic Client Registration fully configured!
</Note>
</Step>
</Steps>

### Step 2: FastMCP Configuration

<Warning>
**Security Best Practice**: For production environments, always configure the `audience` parameter. Without audience validation, your server will accept tokens issued for *any* audience, including tokens meant for completely different services.

**Important**: Keycloak doesn't include the `aud` claim in tokens by default. For the example below to work out-of-the-box, audience validation is disabled. For production, configure Keycloak audience mappers and set `audience` to your resource server identifier (typically your server's base URL) to ensure tokens are specifically intended for your server.
</Warning>

Create your FastMCP server file and use the KeycloakAuthProvider to handle all the OAuth integration automatically:

```python server.py
from fastmcp import FastMCP
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider
from fastmcp.server.dependencies import get_access_token

# The KeycloakAuthProvider automatically discovers Keycloak endpoints
# and configures JWT token validation
auth_provider = KeycloakAuthProvider(
realm_url="http://localhost:8080/realms/fastmcp", # Your Keycloak realm URL
base_url="http://localhost:8000", # Your server's public URL
required_scopes=["openid", "profile"], # Required OAuth scopes
# audience="http://localhost:8000", # For production: configure Keycloak audience mappers first
)

# Create FastMCP server with auth
mcp = FastMCP(name="My Keycloak Protected Server", auth=auth_provider)

@mcp.tool
async def get_access_token_claims() -> dict:
"""Get the authenticated user's access token claims."""
token = get_access_token()
return {
"sub": token.claims.get("sub"),
"name": token.claims.get("name"),
"preferred_username": token.claims.get("preferred_username"),
"scope": token.claims.get("scope")
}
```

## Testing

### Running the Server

Start your FastMCP server with HTTP transport to enable OAuth flows:

```bash
fastmcp run server.py --transport http --port 8000
```

Your server is now running and protected by Keycloak OAuth authentication.

### Testing with a Client

Create a test client that authenticates with your Keycloak-protected server:

```python test_client.py
from fastmcp import Client
import asyncio

async def main():
# The client will automatically handle Keycloak OAuth
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
# First-time connection will open Keycloak login in your browser
print("✓ Authenticated with Keycloak!")

# Test the protected tool
result = await client.call_tool("get_access_token_claims")
print(f"User: {result.data.get('preferred_username', 'N/A')}")

if __name__ == "__main__":
asyncio.run(main())
```

When you run the client for the first time:
1. Your browser will open to Keycloak's authorization page
2. After you log in and authorize the app, you'll be redirected back
3. The client receives the token and can make authenticated requests

<Info>
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
</Info>

### Testing with MCP Inspector

The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) provides an interactive web UI to explore and test your MCP server.

**Prerequisites**: Node.js must be installed on your system.

1. Launch the Inspector:
```bash
npx -y @modelcontextprotocol/inspector
```

2. In the Inspector UI (opens in your browser):
- Enter server URL: `http://localhost:8000/mcp`
- In the **Authentication** section's **OAuth 2.0 Flow** area, locate the **Scope** field
- In the **Scope** field, enter: `openid profile` (these must match the `required_scopes` in your server configuration)
- Click **Connect**
- Your browser will open for Keycloak authentication
- Log in with your test user credentials (e.g., `testuser` / `password123`)
- After successful authentication, you can interactively explore available tools and test them

<Note>
The MCP Inspector requires explicit scope configuration because it doesn't automatically request scopes. This is correct OAuth behavior - clients should explicitly request the scopes they need.
</Note>

The Inspector is particularly useful for:
- Exploring the server's capabilities without writing code
- Testing individual tools with custom inputs
- Debugging authentication and authorization issues
- Viewing request/response details

## Production Configuration

<VersionBadge version="2.13.0" />

For production deployments with persistent token management across server restarts, configure `jwt_signing_key` and `client_storage`:

```python server.py
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider
from key_value.aio.stores.redis import RedisStore
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet

# Production setup with encrypted persistent token storage
auth_provider = KeycloakAuthProvider(
realm_url="https://keycloak.company.com/realms/production",
base_url="https://your-production-domain.com",
required_scopes=["openid", "profile", "email"],
audience="https://your-production-domain.com", # Important for production

# Production token management
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
client_storage=FernetEncryptionWrapper(
key_value=RedisStore(
host=os.environ["REDIS_HOST"],
port=int(os.environ["REDIS_PORT"])
),
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
)
)

mcp = FastMCP(name="Production Keycloak App", auth=auth_provider)
```

<Note>
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.

For complete details on these parameters, see the [Remote OAuth documentation](/servers/auth/remote-oauth#configuration-parameters).
</Note>

## Features

### Dynamic Client Registration (DCR)

The Keycloak provider includes full Dynamic Client Registration support:

- **Automatic Client Registration**: MCP clients automatically register with Keycloak on first connection
- **MCP Compatibility Fix**: Automatically corrects Keycloak's `token_endpoint_auth_method` to meet MCP requirements
- **Auto Re-registration**: If Keycloak is restarted or realm configuration changes, clients automatically re-register

<Info>
If you restart Keycloak or change the realm configuration, your FastMCP client will automatically detect if the cached OAuth client credentials are no longer valid and will re-register with Keycloak automatically. You don't need to manually clear any caches - just run your client again and it will handle the re-registration process seamlessly.
</Info>

### JWT Token Validation

The Keycloak provider includes robust JWT token validation:

- **Signature Verification**: Validates tokens against Keycloak's public keys (JWKS)
- **Expiration Checking**: Automatically rejects expired tokens
- **Issuer Validation**: Ensures tokens come from your specific Keycloak realm
- **Scope Enforcement**: Verifies required OAuth scopes are present
- **Audience Validation**: Optional validation that tokens are intended for your server (configure `audience` parameter)

### User Claims and Attributes

Access rich user information from Keycloak JWT tokens:

```python
from fastmcp.server.dependencies import get_access_token

@mcp.tool
async def admin_only_tool() -> str:
"""A tool only available to admin users."""
token = get_access_token()
user_roles = token.claims.get("realm_access", {}).get("roles", [])

if "admin" not in user_roles:
raise ValueError("This tool requires admin access")

return "Admin access granted!"
```

### Enterprise Integration

Perfect for enterprise environments with:

- **Single Sign-On (SSO)**: Integrate with corporate identity providers (LDAP, Active Directory, SAML)
- **Multi-Factor Authentication (MFA)**: Leverage Keycloak's built-in MFA support
- **Role-Based Access Control**: Use Keycloak roles and groups for fine-grained access control
- **Custom Attributes**: Access custom user attributes defined in your Keycloak realm
- **Compliance**: Meet enterprise security and compliance requirements

## Advanced Configuration

### Custom Token Verifier

For advanced use cases, you can provide a custom token verifier:

```python
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider

# Custom JWT verifier with specific audience
custom_verifier = JWTVerifier(
jwks_uri="http://localhost:8080/realms/fastmcp/protocol/openid-connect/certs",
issuer="http://localhost:8080/realms/fastmcp",
audience="my-specific-client",
required_scopes=["api:read", "api:write"]
)

auth_provider = KeycloakAuthProvider(
realm_url="http://localhost:8080/realms/fastmcp",
base_url="http://localhost:8000",
token_verifier=custom_verifier
)
```
9 changes: 9 additions & 0 deletions examples/auth/keycloak_auth/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Keycloak Configuration
FASTMCP_SERVER_AUTH_KEYCLOAK_BASE_URL=http://localhost:8000
FASTMCP_SERVER_AUTH_KEYCLOAK_REALM_URL=http://localhost:8080/realms/fastmcp

# Optional: Specific scopes
FASTMCP_SERVER_AUTH_KEYCLOAK_REQUIRED_SCOPES=openid,profile

# Optional: Audience validation (recommended for production)
# FASTMCP_SERVER_AUTH_KEYCLOAK_AUDIENCE=http://localhost:8000
Loading