Skip to content

Conversation

@stephaneberle9
Copy link
Contributor

@stephaneberle9 stephaneberle9 commented Sep 27, 2025

Description

This PR implements native Keycloak OAuth support for FastMCP, enabling enterprise-grade authentication for organizations using Keycloak identity and access management infrastructure. The implementation provides a complete authentication provider that handles Keycloak User Realms with robust JWT token validation and Dynamic Client Registration (DCR) support. This contribution also significantly simplifies local development by providing a Docker-based Keycloak setup that allows developers to test OAuth flows instantly without external dependencies.

Contributors Checklist

Review Checklist

  • I have self-reviewed my changes
  • My Pull Request is ready for review

@coderabbitai ignore

- Add AWSCognitoProvider class extending OAuthProxy for Cognito User Pools
- Implement JWT token verification using authlib with JWKS validation
- Support automatic domain construction from prefix and region
- Add comprehensive error handling and debug logging
- Include working example server and client with documentation
- Follow FastMCP authentication provider patterns and standards
- Use existing dependencies (authlib, httpx) without adding new ones
- Add test_aws.py with full test coverage for AWSCognitoProvider
- Test provider initialization, configuration, error handling, and Cognito-specific features (domain construction, scopes, claims)
- Cover error scenarios: invalid tokens, expired tokens, wrong issuer
- Use AsyncMock/MagicMock for comprehensive AWS Cognito API simulation
…stMCP:

- Step-by-step setup guide reflecting AWS Cognito's streamlined UI
- Traditional web application configuration for server-side auth
- JWT token validation and user claims handling
- Environment variable configuration options
- Code examples for server setup and client testing
- Enterprise features including SSO, MFA, and role-based access
- Add get_user_profile tool to server for retrieving authenticated user data
- Update client example to demonstrate profile retrieval functionality
- Fix mistaken documentation examples and improve error handling and data display
- Add commented redirect_path configuration option for better awareness
Remove email, name, and other profile claims from AccessToken as these
are not included in AWS Cognito access tokens per documentation. Keep
only sub, username, and cognito:groups which are the standard claims
available in access tokens for authorization purposes. Update examples
and docs.
Replace duplicate JWT verification logic in AWSCognitoTokenVerifier by
extending JWTVerifier instead of TokenVerifier. This eliminates ~150
lines of duplicated code including JWKS fetching, caching, token
validation, and JWT decoding logic.

Key changes:
- AWSCognitoTokenVerifier now extends JWTVerifier for core JWT operations
- Removed duplicate JWKS/JWT logic and dependencies (httpx, authlib.jose)
- Simplified constructor to configure parent with Cognito URLs and RS256
- Override verify_token() to filter claims to Cognito-specific subset
- Updated tests to work with new inheritance structure

Benefits:
- Eliminates code duplication between JWT providers
- Leverages existing JWT infrastructure and improvements
- Maintains backward compatibility while reducing complexity
- Cleaner separation of JWT verification vs Cognito-specific logic
The timeout_seconds parameter is no longer needed after refactoring
AWSCognitoTokenVerifier to extend JWTVerifier. HTTP timeouts for JWKS
requests are now handled by the parent JWTVerifier class.
…ain_prefix

This change modernizes the AWS Cognito provider by:
- Switching from OAuthProxy to OIDCProxy with automatic OIDC Discovery
- Removing the domain_prefix parameter and related configuration
- Updating get_token_verifier to instantiate AWSCognitoTokenVerifier directly
- Simplifying provider initialization by using Cognito's well-known OIDC endpoints
- Updating documentation and examples to reflect the streamlined configuration
Implement KeycloakAuthProvider that extends RemoteAuthProvider to support
Keycloak integration using OAuth 2.1/OpenID Connect with Dynamic Client
Registration (DCR). The provider automatically discovers OIDC endpoints
and forwards authorization server metadata to enable seamless client
authentication.

Key features:
- Automatic OIDC endpoint discovery from Keycloak realm
- JWT token verification with JWKS support
- Authorization server metadata forwarding for DCR
- Configurable scope requirements and custom token verifiers
- Environment variable configuration support

Includes comprehensive example with:
- Docker Compose setup with Keycloak 26.2
- Pre-configured test realm with client and user
- Complete server and client demonstration
- Automated setup script with health checks
- Detailed documentation and troubleshooting guide
…ications in client registration responses for Keycloak OAuth provider

Enhances the existing Keycloak authentication provider with automatic scope management
to eliminate client-side scope configuration requirements.

Key improvements:
- Server-side injection of required scopes into client registration and authorization requests
- Automatic FastMCP compatibility modifications of Keycloak client registration responses
- Updated Keycloak test realm configuration to resolve trusted host and duplicate client scope issues
- Enhanced example with proper scope handling and user claim access
Remove hardcoded fastmcp-client configuration and add DCR policy and profile to enable
dynamic client registration. This allows clients to register automatically at runtime rather
than requiring pre-configured client entries.
Implement complete test coverage for the Keycloak authentication provider with 23 comprehensive tests (16 unit tests, 7 integration tests) covering all aspects of OAuth integration and Dynamic Client Registration (DCR).

Key Features Tested:
- Dynamic Client Registration (DCR) with scope injection
- FastMCP compatibility modifications (auth method, response types)
- OAuth proxy architecture for CORS prevention
- Server-configured required scopes automatic injection
- JWT token verification with JWKS integration
- Complete inheritance from RemoteAuthProvider

All tests pass with zero warnings and verify the provider is production-ready for both Docker development environments and enterprise Keycloak deployments.
Docker setup

- Upgrade Keycloak image from 26.2 to 26.3
- Rename realm-export.json to realm-fastmcp.json for clarity
- Simplify docker-compose.yml configuration by removing unnecesary
  settings and relying on defaults instead
- Add Docker network gateway IP (172.17.0.1) to trusted hosts for
  improved container networking
- Update realm configuration to use cleaner policy and profile names
Keycloak restart in auth example

- Include detailed comments explaining the Keycloak
   restart scenario and the "We are sorry... Client
  not found" error that may show up
- Add a code snippet enabling users to easily clear their
  OAuth cache when running their client in such situations
- Create complete Keycloak integration guide with step-by-step
  setup instructions
- Include Docker setup examples and realm configuration import
  process
- Document Dynamic Client Registration (DCR) configuration and
  troubleshooting
- Provide environment variable configuration options and examples
- Include advanced configuration scenarios with custom token
  verifiers
- Add troubleshooting section for resolution of common
  "Client not found" error in Keycloak restart scenarios
- Update docs navigation to include Keycloak integration
@marvin-context-protocol marvin-context-protocol bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality. auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. labels Sep 27, 2025
Copy link
Owner

@jlowin jlowin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like there's way too much going on in this PR. Did an LLM author this?

  • The example should be a minimal server/client pair like all other auth providers. There should definitely not be a docker-compose file.
  • keycloak.py seems to reimplement our oauthproxy AND oidcproxy while still claiming (in the linked issue) to natively support DCR? Ideally this would be a minor amount of configuration against one of FastMCP's establish auth classes.

Split the verbose Keycloak README.md to improve clarity and match the
simplicity of other auth provider examples:

- Move Keycloak setup details to keycloak/README.md
- Simplify main README.md with two clear options:
  - Option A: Local Keycloak instance (automatic realm import)
  - Option B: Existing Keycloak instance (manual realm import)
- Reorganize Docker files into keycloak/ subdirectory
- Rename setup.sh to start-keycloak.sh for clarity
- Remove Python venv setup from start script (focus on Keycloak only)
- Add prerequisites, troubleshooting, and configuration details to
  keycloak/README.md
- Clarify that realm is auto-imported for local Docker setup
- Add note about restarting Keycloak after configuration changes
Convert AnyHttpUrl types to str for httpx and JWTVerifier compatibility:
- Cast registration_endpoint to str in httpx.post() call
- Cast jwks_uri and issuer to str in JWTVerifier initialization
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f5622c and ccc8b71.

📒 Files selected for processing (1)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/server/auth/providers/keycloak.py (5)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccc8b71 and 1c9afdb.

📒 Files selected for processing (1)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/server/auth/providers/keycloak.py (4)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest

Address CodeRabbit review feedback:
- Add 10s timeout to OIDC discovery to prevent startup hangs
- Add 10s timeout to httpx client for registration requests
- Improve error handling: wrap response.json() in try/catch and preserve
  Keycloak error details in error_description field
- Simplify URL conversions: remove redundant None checks after discovery
  applies defaults (keep str() conversions for type safety)
@stephaneberle9 stephaneberle9 force-pushed the feat/keycloak-auth-provider branch from 1c9afdb to 67af227 Compare November 3, 2025 19:59
Address two proxy-related bugs identified in code review:

1. Content-Length header mismatch: Exclude hop-by-hop headers
   (content-length, transfer-encoding) when forwarding client
   registration requests, allowing httpx to recompute the correct
   Content-Length after body modifications.

2. Multi-valued query parameter loss: Use multi_items() instead of
   dict() when proxying authorization requests to preserve duplicate
   query parameters (e.g., multiple 'resource' params per RFC 8707).

These fixes ensure proper HTTP proxying behavior and maintain full
compatibility with OAuth 2.0 and RFC 8707 standards.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67af227 and 57648de.

📒 Files selected for processing (1)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/server/auth/providers/keycloak.py (5)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest

Fix a security issue where provider-configured required scopes were
silently ignored when users supplied custom token verifiers. This
allowed provider-level scope requirements to be bypassed.

Now merges provider-level required scopes into custom verifiers
before initialization, ensuring:
- Provider-mandated scopes are always enforced
- Existing custom verifier scopes are preserved
- No duplicate scopes are added
- Both proxy logic and token verification respect scope requirements

This maintains the security model where provider-level scope policies
cannot be circumvented by supplying a custom verifier.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57648de and 0c767c7.

📒 Files selected for processing (1)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/server/auth/providers/keycloak.py (4)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest

Address three robustness issues identified in code review:

1. Graceful handling of immutable verifiers: Wrap scope mutation in
   try-except to handle verifiers with frozen/validated required_scopes
   attribute. Logs a warning instead of crashing when mutation fails.

2. Avoid double-decoding responses: Cache response.content once and
   reuse it to prevent stream rewinding errors that occur when
   response.json() is called multiple times on streaming responses.

3. Preserve multi-valued query params: Add doseq=True to urlencode()
   to properly preserve duplicate query parameters (e.g., multiple
   'resource' params per RFC 8707) that were kept by multi_items().

These fixes ensure robust HTTP proxying with various verifier
implementations, large payloads, and RFC 8707 compliance.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c767c7 and 6c55017.

📒 Files selected for processing (1)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/fastmcp/server/auth/providers/keycloak.py (4)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (2)
src/fastmcp/server/auth/providers/keycloak.py (2)

173-195: LGTM!

The OIDC discovery implementation correctly fetches configuration from Keycloak, applies sensible defaults for missing fields, and includes appropriate timeout handling to prevent startup hangs.


246-445: Excellent proxy implementation!

The three proxy endpoints (registration, authorization, and metadata) are well-implemented with proper:

  • Scope injection and merging without duplication
  • HTTP header handling (excluding hop-by-hop headers)
  • Multi-valued query parameter preservation using multi_items() and doseq=True
  • Response body caching to avoid double-decode issues
  • Comprehensive error handling that preserves Keycloak error details
  • Appropriate timeouts to prevent hangs

All the issues raised in previous review rounds have been properly addressed.

Implement configurable JWT audience validation to address security
concerns about accepting tokens intended for any resource server.

Changes:
- Add `audience` parameter to KeycloakProviderSettings and
  KeycloakAuthProvider constructor
- Pass audience to JWTVerifier instead of hardcoded None
- Add FASTMCP_SERVER_AUTH_KEYCLOAK_AUDIENCE environment variable
- Update docstrings with security notes about audience validation
- Update documentation (keycloak.mdx) with security warning and
  configuration examples
- Update example code to demonstrate audience validation with
  base_url as default

Security impact:
By default, audience validation remains disabled for backward
compatibility and DCR flexibility. However, documentation now
prominently encourages production deployments to configure
audience validation to prevent accepting tokens issued for
other services.

Recommended configuration:
  audience="https://your-server.com"  # or base_url
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c55017 and e75a34a.

📒 Files selected for processing (5)
  • docs/integrations/keycloak.mdx (1 hunks)
  • examples/auth/keycloak_auth/.env.example (1 hunks)
  • examples/auth/keycloak_auth/README.md (1 hunks)
  • examples/auth/keycloak_auth/server.py (1 hunks)
  • src/fastmcp/server/auth/providers/keycloak.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write instructions and procedures in second person ("you")
Use active voice over passive voice
Use present tense for current states and future tense for outcomes
Maintain consistent terminology across the documentation
Keep sentences concise while preserving necessary context
Use parallel structure in lists, headings, and procedures
Lead with the most important information (inverted pyramid)
Use progressive disclosure: basic concepts before advanced ones
Break complex procedures into numbered steps
Include prerequisites and context before instructions
Provide expected outcomes for each major step
End sections with next steps or related information
Use descriptive, keyword-rich headings for navigation and SEO
Focus on user goals and outcomes rather than system features
Anticipate common questions and address them proactively
Include troubleshooting for likely failure points
Offer multiple pathways when appropriate (beginner vs advanced) and provide an opinionated recommended path
Use for supplementary information that supports the main content
Use for expert advice, shortcuts, or best practices
Use for critical cautions, breaking changes, or destructive actions
Use for neutral background or contextual information
Use to confirm success or completion
Provide single code examples using fenced code blocks with language (and filename when relevant)
Use to present the same concept in multiple languages
For API docs, use to show requests
For API docs, use to show responses
Use and to document procedures and sequential instructions
Use and for platform-specific or alternative approaches
Use / for supplementary content that might interrupt flow
In API docs, use for parameters (path, body, query, header) with type and required/default as appropria...

Files:

  • docs/integrations/keycloak.mdx
🧠 Learnings (10)
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Confirm proper heading hierarchy with H2 for main sections, H3 for subsections

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Ensure proper heading hierarchy starting with H2 for main sections and H3 for subsections

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Add appropriate <Warning> callouts for destructive or security-sensitive actions

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Use <Warning> for critical cautions, breaking changes, or destructive actions

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Check for consistency in terminology, formatting, and component usage

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Use parallel structure in lists, headings, and procedures

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Include verification/testing steps with expected outcomes

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Maintain consistent terminology across the documentation

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Use <Tabs> for platform-specific content or alternative approaches

Applied to files:

  • examples/auth/keycloak_auth/README.md
📚 Learning: 2025-10-27T14:38:52.643Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-10-27T14:38:52.643Z
Learning: Applies to docs/**/*.mdx : Include troubleshooting for likely failure points

Applied to files:

  • examples/auth/keycloak_auth/README.md
🧬 Code graph analysis (2)
examples/auth/keycloak_auth/server.py (4)
src/fastmcp/server/server.py (1)
  • FastMCP (149-2699)
src/fastmcp/server/auth/providers/keycloak.py (1)
  • KeycloakAuthProvider (47-470)
src/fastmcp/server/dependencies.py (1)
  • get_access_token (104-135)
src/fastmcp/utilities/logging.py (1)
  • configure_logging (29-95)
src/fastmcp/server/auth/providers/keycloak.py (4)
src/fastmcp/server/auth/auth.py (2)
  • RemoteAuthProvider (192-262)
  • TokenVerifier (166-189)
src/fastmcp/server/auth/oidc_proxy.py (1)
  • OIDCConfiguration (27-169)
src/fastmcp/server/auth/providers/jwt.py (1)
  • JWTVerifier (165-498)
src/fastmcp/utilities/auth.py (1)
  • parse_scopes (9-34)
🪛 dotenv-linter (4.0.0)
examples/auth/keycloak_auth/.env.example

[warning] 3-3: [UnorderedKey] The FASTMCP_SERVER_AUTH_KEYCLOAK_BASE_URL key should go before the FASTMCP_SERVER_AUTH_KEYCLOAK_REALM_URL key

(UnorderedKey)


[warning] 10-10: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest

Address code review feedback:

1. Fix broken Docker Compose link: Update path to point to
   keycloak/docker-compose.yml subdirectory in documentation

2. Fix .env.example formatting: Alphabetize environment variable keys
   (BASE_URL before REALM_URL) and add trailing newline to satisfy
   dotenv-linter

3. Fix environment loading order: Call load_dotenv() before
   configure_logging() in example server to ensure logging-related
   environment variables from .env are properly applied

These changes improve documentation accuracy and ensure the example
follows best practices for environment variable handling.
@jlowin
Copy link
Owner

jlowin commented Nov 4, 2025

I'm uncomfortable merging this PR due to the architectural complexity and maintenance burden it introduces. This PR is over 2000 lines including the example, uses a radically different architecture from other auth integrations (namely, operational HTTP routes akin to our proxy architectures), and requires modifications that make it fundamentally independent from our core auth architecture, meaning it won't benefit from future shared auth improvements or MCP patterns.

The implementation proxies multiple HTTP endpoints with request/response transformations to inject scopes, modify authentication methods, and rewrite metadata URLs. This degree of intervention suggests either: (a) Keycloak's DCR implementation has fundamental incompatibilities with MCP clients, or (b) there's a simpler solution using Keycloak configuration or existing FastMCP abstractions. The point of the remote auth provider is specifically that the server is not acting as an auth broker in any way; is there a fundamental bug in our implementation?

I know Keycloak is extremely popular and standards-compliant, which makes me skeptical that it genuinely requires this unique approach. The scope injection in particular raises security questions - if clients aren't requesting required scopes themselves, we may be creating authorization bypass risks.

As a path forward, I need you to demonstrate one of the following:

  1. Keycloak works with RemoteAuthProvider directly - Show that a minimal implementation (~30-50 lines) using standard DCR works, with any Keycloak-specific configuration documented. This is the preferred outcome.
  2. Keycloak's DCR is nonstandard, use OIDCProxy - If Keycloak's DCR doesn't work with MCP client flows, use the existing OIDCProxy pattern (requires pre-registered client credentials but no custom proxying).
  3. The route proxying is genuinely necessary - Provide a technical writeup as an issue explaining specifically what breaks without each proxy endpoint, why Keycloak configuration can't solve it, and why existing FastMCP abstractions don't work. Identify whether these are FastMCP bugs or Keycloak idiosyncrasies (or both).

If none of these work, or if option 3 is valid, I'd recommend publishing this provider as fastmcp-keycloak, a separate package, as we can't maintain this level of provider-specific customization in the core library.

Can you test option 1 (minimal RemoteAuthProvider) and report back what specifically breaks?

@stephaneberle9
Copy link
Contributor Author

Thank you for your detailed feedback. I fully understand your concerns and agree that we need to strike a reasonable balance between introducing new features/providers and avoiding unnecessary codebase bloat. I’ll review the options you suggested, and we’ll see which approach makes the most sense. Please just keep in mind that this may take a little time.

Enhances start-keycloak.sh to automatically detect and support both Docker Compose v1 (docker-compose) and v2 (docker compose) commands. Adds helpful installation instructions if Docker Compose is not found, and displays version-specific commands in the output.
OAuth client now automatically handles stale credentials and re-registers
with Keycloak when needed. Manual cache clearing is no longer required.
Keycloak tokens don't include 'aud' claim by default. Changed example to
disable audience validation for development (audience=None). Production
deployments should configure Keycloak audience mappers and set the
FASTMCP_SERVER_AUTH_KEYCLOAK_AUDIENCE environment variable.
Enable MCP Inspector support for the Keycloak OAuth example by addressing
three key compatibility requirements:

1. CORS Configuration (server.py):
   - Add CORSMiddleware with proper headers for browser-based clients
   - Expose mcp-session-id header required for stateful HTTP transport
   - Configure allowed headers: mcp-protocol-version, mcp-session-id, Authorization
   - Reference: https://gofastmcp.com/deployment/http#cors-for-browser-based-clients

2. Trusted Hosts (realm-fastmcp.json):
   - Add github.com to trusted hosts for MCP Inspector origin
   - Enable dynamic client registration from Inspector's GitHub-hosted UI

3. Offline Access Support (realm-fastmcp.json):
   - Add defaultOptionalClientScopes: ["offline_access"]
   - Grant offline_access realm role to test user
   - Enable long-lived refresh tokens for Inspector sessions

Additional improvements:
- Remove FileTokenStorage in favor of automatic retry mechanism
- Disable audience validation by default (Keycloak doesn't include aud claim)
- Simplify realm configuration (minimal scopes: openid, profile, email, offline_access)
- Add comprehensive MCP Inspector documentation to README
- Update troubleshooting guide with Inspector-specific restart instructions
- Document Docker Compose v2 syntax and realm configuration reload process

Both Python client and MCP Inspector now work seamlessly with Keycloak OAuth.
@stephaneberle9 stephaneberle9 force-pushed the feat/keycloak-auth-provider branch from 717dcb7 to 2e546e5 Compare November 17, 2025 09:15
…win#1937)

This commit drastically simplifies the Keycloak provider from a full OAuth proxy
to a minimal DCR-only workaround, implementing the suggested "option 1" approach
from PR feedback.

**Architectural change: 3 proxy routes → 1 proxy route**

BEFORE (full OAuth proxy):
- /authorize proxy (authorization endpoint with scope injection)
- /register proxy (full DCR with request/response modifications)
- /.well-known/oauth-authorization-server (metadata with modifications)
- Complex OIDC discovery and configuration management

AFTER (minimal DCR proxy):
- /register proxy (fixes only token_endpoint_auth_method field)
- /.well-known/oauth-authorization-server (simple forwarding)
- Hard-coded Keycloak URL patterns (no OIDC discovery)
- Authorization flows, token issuance, and validation go directly to Keycloak

**Why the minimal proxy is needed:**

Keycloak ignores the client's requested token_endpoint_auth_method and always
returns "client_secret_basic", but MCP requires "client_secret_post" per RFC 9110.
The minimal proxy intercepts only DCR responses to fix this single field.

**Changes:**

- Reduce full OAuth proxy to minimal DCR proxy (as detailed above)

- Simplify realm configuration:
  * Remove clientProfiles/clientPolicies (executors unavailable in Keycloak 26.3+)
  * Use realm-level default scopes and explicit clientScopes definitions

- Update documentation:
  * Add MCP Compatibility Note explaining the workaround
  * Update MCP Inspector instructions with scope requirements

- Add integration test proving Keycloak's DCR limitation and verifying the fix

Addresses: jlowin#1937 (comment)
@stephaneberle9
Copy link
Contributor Author

Can you test option 1 (minimal RemoteAuthProvider) and report back what specifically breaks?

I've implemented option 1 as closely as possible. The Keycloak provider now adds only 1 additional route (/register) compared to a standard RemoteAuthProvider, making it a truly minimal implementation.

Why the DCR proxy is necessary: Keycloak has a bug where it ignores the client's requested token_endpoint_auth_method during Dynamic Client Registration and always returns client_secret_basic, even when clients explicitly request client_secret_post (which MCP requires per RFC 9110). The minimal proxy intercepts only DCR responses to fix this single field. All other OAuth operations (authorization, token issuance, validation) go directly to Keycloak.

The inconsistent token_endpoint_auth_method behavior has been filed as a bug to Keycloak : keycloak/keycloak#44403

Adds missing 'basic' client scope to the fastmcp realm configuration
to resolve Keycloak warning: "Referenced client scope 'basic' doesn't
exist. Ignoring"

The scope is configured as an openid-connect protocol scope that is
included in tokens but not displayed on the consent screen.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Keycloak OAuth Authentication Provider leveraging Dynamic Client Registration (DCR)

4 participants