Skip to main content

7. Pluggable Authentication Provider Pattern

Date: 2025-10-13

Status

Accepted

Category

Core Architecture

Context

Enterprise applications require integration with existing identity providers (Keycloak, Okta, Auth0, Azure AD), while development and testing environments benefit from simpler authentication mechanisms. A hardcoded authentication approach creates several problems:
  • Enterprise Adoption Barrier: Cannot integrate with existing SSO infrastructure
  • Development Friction: Requires full SSO setup for local development
  • Testing Complexity: Integration tests require live identity provider
  • Vendor Lock-in: Switching identity providers requires code changes
  • Multi-Tenant Challenges: Different customers may require different auth providers
Real-world requirements:
  • Development: Simple username/password without external dependencies
  • Enterprise: Keycloak SSO with OIDC/OAuth2, MFA, role synchronization
  • Testing: Mock provider with predictable behavior
  • Future: Okta, Auth0, Azure AD, custom LDAP integration

Decision

We will implement a pluggable authentication provider pattern using the Abstract Base Class (ABC) design pattern with multiple concrete implementations.

Architecture

Factory Pattern

Configuration

Consequences

Positive Consequences

  • Enterprise Ready: Seamless SSO integration with Keycloak, Okta, etc.
  • Development Speed: No SSO setup required for local development
  • Testability: Mock providers for fast, isolated unit tests
  • Flexibility: Switch providers via configuration (no code changes)
  • Extensibility: New providers (Okta, Auth0) add easily
  • Multi-Tenant Support: Different customers can use different providers

Negative Consequences

  • Complexity: Multiple implementations to maintain
  • Feature Parity: Not all providers support all features (e.g., MFA)
  • Testing Burden: Each provider requires separate test coverage
  • Interface Leakage: Provider-specific features may not fit abstraction

Neutral Consequences

  • Token Format Variance: JWT structure differs between providers
  • Configuration Overhead: More environment variables to manage

Alternatives Considered

1. Keycloak Only (Direct Integration)

Pros: Simpler (single code path), full feature access Cons: Requires Keycloak for development, cannot test without it, vendor lock-in Why Rejected: Development friction and vendor lock-in outweigh simplicity

2. Passport.js Strategy Pattern

Pros: Well-established pattern, many existing strategies Cons: JavaScript library (not Python), heavier abstraction Why Rejected: Not Python-native, over-engineered for our needs

3. OAuth2/OIDC Generic Client

Pros: Works with any OIDC provider, standard protocol Cons: Does not support non-OIDC providers, complex configuration Why Rejected: Limits non-OIDC providers (e.g., simple API key auth)

4. Hardcoded Multi-Provider Support

Pros: No abstraction overhead, direct provider access Cons: if/elif chains throughout codebase, hard to extend Why Rejected: Unmaintainable as provider count grows

Implementation Details

InMemoryUserProvider

Features:
  • In-memory user dictionary
  • Simple password validation
  • Instant response (no network calls)
  • Pre-configured test users
Use Cases: Development, unit tests, demos

KeycloakUserProvider

Features:
  • OIDC/OAuth2 integration
  • JWT verification with JWKS
  • Automatic role synchronization to OpenFGA
  • Token refresh support
  • MFA support (via Keycloak)
Use Cases: Production, staging, enterprise deployments

JWT Verification (Keycloak)

Security Features:
  • Public key verification (no shared secrets)
  • JWKS caching (performance optimization)
  • Signature verification (prevents tampering)
  • Claims validation (audience, issuer, expiration)

Integration Points

AuthMiddleware (src/mcp_server_langgraph/auth/middleware.py):
  • Accepts user_provider: UserProvider parameter
  • Defaults to InMemoryUserProvider() for backward compatibility
  • Calls provider methods: authenticate, get_user, refresh_token
Role Synchronization (src/mcp_server_langgraph/auth/keycloak.py:545):

Testing Strategy

Unit Tests (tests/test_user_provider.py):
  • InMemoryUserProvider: 20/20 tests passing (100%)
  • KeycloakUserProvider interface: 15/20 tests passing (needs Keycloak mock improvements)
  • Factory function: 5/5 tests passing (100%)
  • JWT verification: 10/10 tests passing (100%)
Integration Tests (tests/test_keycloak.py):
  • End-to-end authentication flow
  • Token refresh
  • Role synchronization

Migration Path

Existing deployments: No migration required (defaults to InMemory) Enabling Keycloak:
  1. Deploy Keycloak instance
  2. Create realm and client
  3. Set AUTH_PROVIDER=keycloak
  4. Configure Keycloak settings
  5. Restart application

Future Enhancements

  • Okta provider (OktaUserProvider)
  • Auth0 provider (Auth0UserProvider)
  • Azure AD provider (AzureADUserProvider)
  • LDAP provider (LDAPUserProvider)
  • API key provider (APIKeyUserProvider)

References

  • Implementation: src/mcp_server_langgraph/auth/user_provider.py:1-400
  • Factory Pattern: src/mcp_server_langgraph/auth/factory.py:1-188 (recommended for production)
  • Keycloak Client: src/mcp_server_langgraph/auth/keycloak.py:1-650
  • Tests: tests/test_user_provider.py, tests/test_keycloak.py, tests/test_auth_factory.py
  • AuthMiddleware: src/mcp_server_langgraph/auth/middleware.py
  • Related ADRs:
    • ADR-0006 - Session storage (used with auth)
    • ADR-0002 - Authorization (role sync target)