Traefik OIDC Middleware

repository·main·Indexed 41 days ago

https://github.com/lukaszraczylo/traefikoidc

An OpenID Connect authentication solution for Traefik that replaces the need for combining forward-auth with oauth2-proxy. It automates provider detection, validates ID tokens, manages user sessions, and forwards identity to downstream services. Features include support for Machine-to-Machine (M2M) authentication, private key JWT client authentication (RFC 7523), Redis for multi-replica session state, and templated headers for identity forwarding.

Tokens
54.5K
Snippets
146
Records
212
Agent score
52%

What's inside Traefik OIDC Middleware

  1. How automatic scope filtering works

    main

    The middleware includes an automatic scope filtering mechanism to prevent authentication failures caused by requesting unsupported scopes.

    The Process

    1. The middleware fetches the provider's .well-known/openid-configuration.
    2. It extracts the scopes_supported field from the discovery document.
    3. It compares your requested scopes against the supported list.
    4. It filters out any requested scopes that the provider does not support.
    5. If the provider does not declare supported scopes, the middleware falls back to using all requested scopes.

    Example: Self-Hosted GitLab

    If you request offline_access but GitLab's discovery document does not list it in scopes_supported, the middleware will automatically remove it before sending the request, allowing authentication to succeed.

    Troubleshooting Scope Issues

    If a provider still rejects scopes after filtering:

    1. Verify the provider's capabilities by running: curl https://provider/.well-known/openid-configuration.
    2. Use the overrideScopes: true option and provide only the scopes known to be supported.
    3. Check the middleware logs for ScopeFilter entries to see which scopes were removed.
  2. Use JWT Bearer Assertion (RFC 7523) for Secretless Auth

    main

    If shared secrets are not permitted by your security policy, you can use the JWT bearer assertion flow. Instead of a client_secret, the client provides a signed JWT assertion to the IdP.

    Assertion Requirements: The assertion JWT must contain:

    • iss: The client's ID
    • sub: The client's ID
    • aud: The IdP's token endpoint
    • exp: Expiration time

    Request Format:

    POST /token
    grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
    assertion=<JWT signed by the client's private key>
  3. How Bearer Token Authentication works

    main

    Bearer Token authentication allows Machine-to-Machine (M2M) clients to access protected resources using a JWT in the Authorization: Bearer <token> header.

    Authentication Lifecycle

    1. Detection: The middleware detects the Authorization: Bearer prefix.
    2. Validation: The token undergoes several checks:
      • Format: Length and segment count (must have exactly two dots).
      • Security: JOSE header validation (algorithm/kid pinning) and per-IP 401 throttling.
      • Signature & Claims: Verifies signature, issuer, audience, and expiration. It also rejects ID tokens (tokens where token_use == "id" or a nonce is present).
      • Age: Rejects tokens where time.Now() - iat > MaxTokenAgeSeconds.
      • Introspection (Optional): If requireTokenIntrospection is enabled, the token is checked against the OIDC provider's introspection endpoint.
    3. Identifier Resolution: The middleware extracts the user identifier from the claim specified by BearerIdentifierClaim (defaults to sub). The identifier is sanitized to prevent injection attacks.
    4. Authorization: The middleware checks roles and groups extracted from the claims against allowedRolesAndGroups.
    5. Forwarding: If authorized, the middleware injects headers (X-Forwarded-User, X-User-Groups, X-User-Roles, etc.) and forwards the request to the backend. If StripAuthorizationHeader is enabled, the Authorization header is removed.
  4. Frequently Asked Questions about Redis Cache

    main

    Is Redis required?

    No, Redis is optional. The plugin works with in-memory cache for single-instance deployments.

    What happens if Redis goes down?

    The circuit breaker opens after threshold failures, and the plugin falls back to in-memory cache. It periodically attempts to reconnect.

    Which cache mode should I use?

    For production multi-replica deployments, use hybrid mode for best performance and consistency.

    Can I use managed Redis services?

    Yes, the plugin works with AWS ElastiCache, Azure Cache for Redis, Google Cloud Memorystore, and Redis Enterprise Cloud.

    Is data encrypted in Redis?

    Session data is encrypted before storing using sessionEncryptionKey. Additionally, you can enable TLS for Redis connections.

  5. How streaming endpoints (SSE and WebSocket) are handled

    main

    The middleware automatically bypasses the OIDC redirect for requests that cannot follow a 302 redirect, specifically:

    • Server-Sent Events (SSE): Triggered by Accept: text/event-stream.
    • WebSocket upgrades: Triggered by Upgrade: websocket + Connection: upgrade.

    Important Security Notes:

    • This bypass is not unauthenticated. A valid, encrypted session cookie (sealed with sessionEncryptionKey) is required. If no valid cookie is present, the connection is rejected.
    • Validation is performed using the session cookie only; it does not perform a JWK fetch. This allows streaming to continue even if the OIDC provider is temporarily unavailable.
    • The user must first complete the standard OIDC flow on a regular HTTP page to obtain the session cookie before establishing a streaming connection.
    • The user identifier is forwarded as X-Forwarded-User (and X-Auth-Request-User unless minimalHeaders: true).
  6. Compare `resource` and `audience` parameters

    main

    While both resource and audience are used for audience validation, they result in different requests being sent to the Identity Provider:

    Featureaudienceresource (RFC 8707)
    Request ParameterSent as a non-standard audience parameter.Sent as the standard resource parameter.
    Default BehaviorRequires explicit configuration.If resource is set and audience is not, the expected-audience check defaults to the resource value (validation only; no audience parameter is sent).
    CoexistenceN/AYou can set both. If both are configured, both parameters are sent to the IdP (useful as a hedge against providers that ignore resource).
  7. Configure client authentication methods

    main

    The middleware supports three methods for authenticating the client at token and revocation endpoints via the clientAuthMethod parameter:

    1. client_secret_post (Default): Sends client_id and client_secret as form parameters in the request body. No extra config needed.
    2. client_secret_basic: Sends credentials in the Authorization: Basic header (RFC 6749 §2.3.1). Use this if your IdP rejects credentials in the request body.
    3. private_key_jwt: Uses asymmetric authentication (RFC 7523 §2.2). The plugin signs a short-lived JWT using a private key. This is recommended for high-security environments or when using IdPs with short secret TTLs.
    # Example for client_secret_basic
    clientAuthMethod: client_secret_basic
    clientID: your-client-id
    clientSecret: your-client-secret
  8. Understand Bearer Token edge cases and security behaviors

    main

    When using Bearer authentication, be aware of the following operational behaviors:

    • Case Insensitivity: The Bearer prefix check is case-insensitive (e.g., bearer, BEARER, BeArEr are all valid).
    • Header Precedence: If multiple Authorization headers are present, only the first one is used. If both a Bearer token and a cookie are present, the cookie wins unless bearerOverridesCookie is set to true.
    • Excluded URLs: If a URL is marked as excluded, the Bearer token is not validated. However, the Authorization: Bearer header is automatically stripped before forwarding to prevent the token from leaking into downstream logs or metrics.
    • Streaming (SSE/WebSocket): By default, SSE and WebSocket bypass paths use cookie-only behavior. To use Bearer tokens for streaming, you must manually remove the SSE/WS bypass configuration.
    • Logout: The logout endpoint processes cookie-session logouts. To revoke a Bearer token, you must perform a revocation action at the Identity Provider (IdP) level (or use introspection).
  9. Obtain Bearer Tokens from an OIDC Provider

    main

    The middleware validates tokens but does not mint them. For M2M traffic, you must use your Identity Provider (IdP) to obtain an access token, typically via the OAuth 2.0 client_credentials grant type.

    The Flow:

    1. Client → IdP: Client requests a token using grant_type=client_credentials, providing client_id, client_secret (or a JWT assertion), and the required audience.
    2. IdP → Client: IdP returns a JWT access_token.
    3. Client → Middleware: Client calls the protected service with the header Authorization: Bearer <access_token>.
    4. Middleware → Backend: The middleware validates the token (signature, issuer, audience, expiration) and forwards the request with the X-Forwarded-User header set.
  10. How Bearer Token and Cookie authentication interact

    main

    The middleware handles requests containing both session cookies and Bearer tokens using a specific precedence model:

    1. Default Behavior (Cookie Wins): If both a session cookie and an Authorization: Bearer header are present, the middleware prioritizes the cookie. This is a security measure for browser-based scenarios. A warning is logged when this occurs.
    2. Override Behavior: If you set BearerOverridesCookie=true, the Bearer token will take precedence over the cookie.
    3. SSE/WS Bypass: For Server-Sent Events (SSE) or WebSocket (WS) connections, the middleware ignores the Bearer header and maintains the existing cookie-only check.
    4. Excluded URLs: When a request hits an excluded URL, the Authorization header is stripped before forwarding to prevent leaking credentials into downstream logs or public endpoints.
  11. How to choose the right mock type

    main

    The project provides three distinct mocking patterns. Choose the one that best fits your testing requirements:

    1. Basic State-Based Mocks (e.g., MockJWKCache): Use when you only need to control return values and do not need to verify that specific methods were called.
    2. Enhanced State-Based Mocks (e.g., EnhancedMockJWKCache): Use when you want to verify calls were made with specific parameters using built-in assertion helpers (like AssertGetJWKSCalledWith), but prefer a simpler setup than testify.
    3. Testify-Based Mocks (e.g., TestifyJWKCache): Use for complex behavior, such as returning different values on consecutive calls, strict call ordering, or detailed expectation matching using the .On().Return() pattern.

    Other specialized mocks:

    • MockOAuthProvider: Use for simulating a full HTTP OAuth provider endpoint.
    • testutil.OIDCServer: Use for full integration tests against a realistic OIDC provider.
  12. How Dynamic Client Registration (DCR) works

    main

    Dynamic Client Registration (RFC 7591) allows the middleware to automatically register itself with an OIDC provider at startup. This eliminates the need for pre-provisioned clientID and clientSecret values, making it ideal for multi-tenant, self-service, or ephemeral environments.

    Workflow

    1. The middleware discovers the registration_endpoint from the provider's .well-known/openid-configuration.
    2. If no clientID is provided in the configuration, the middleware POSTs the configured clientMetadata to that endpoint.
    3. The returned client_id and client_secret are cached and can be persisted for reuse.
    4. All subsequent authentication requests use these dynamically registered credentials.

    Multi-replica Deployments

    In environments with multiple replicas, you must set storageBackend: redis to ensure all instances share the same registered client and to prevent race conditions where multiple replicas attempt to register simultaneously.

    # Example snippet for multi-replica setup
    dynamicClientRegistration:
      enabled: true
      persistCredentials: true
      storageBackend: redis
      redisKeyPrefix: "dcr:creds:"