Files
traefikoidc/docs/CONFIGURATION.md
T
lukaszraczylo 68c150eba4 fix(cache/redis): honor enableTLS for Redis backend (#133)
The redis.enableTLS / redis.tlsSkipVerify settings were accepted by the
config layer but silently dropped before reaching the connection pool, so
the plugin always dialed Redis in plaintext. This blocked TLS-only Redis
deployments such as AWS ElastiCache with in-transit encryption.

- Add EnableTLS, TLSSkipVerify, TLSServerName to backends.Config and
  PoolConfig and forward them through universal_cache_singleton ->
  backends.Config -> PoolConfig.
- In the connection pool, dial via tls.Dialer.DialContext (TLS 1.2
  minimum) with SNI defaulting to the host part of the configured
  Address when TLSServerName is empty, so ElastiCache cluster endpoints
  validate out of the box. Plain dial path now also propagates ctx.
- Add regression tests covering successful TLS negotiation with skip-
  verify, rejection of self-signed certs without skip-verify, rejection
  of plain TCP servers when EnableTLS=true, and unaffected plaintext
  behavior.
- Document maxRefreshTokenAgeSeconds (added in 1b6c861) and the implicit
  SSE / WebSocket auth bypass (added in 684a990) in README.md,
  docs/CONFIGURATION.md and docs/index.html.
- Add the missing redis.tlsSkipVerify row to docs/index.html and clarify
  the redis.enableTLS description.

patch-release
2026-05-07 12:24:13 +01:00

14 KiB

Configuration Reference

Complete reference for all Traefik OIDC middleware configuration options.

Table of Contents


Required Parameters

Parameter Type Description Example
providerURL string Base URL of the OIDC provider https://accounts.google.com
clientID string OAuth 2.0 client identifier 1234567890.apps.googleusercontent.com
clientSecret string OAuth 2.0 client secret your-client-secret
sessionEncryptionKey string Key for encrypting session data (min 32 bytes) your-32-byte-encryption-key-here
callbackURL string Path where provider redirects after authentication /oauth2/callback

Basic Configuration Example

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: oidc-auth
spec:
  plugin:
    traefikoidc:
      providerURL: https://accounts.google.com
      clientID: your-client-id.apps.googleusercontent.com
      clientSecret: your-client-secret
      sessionEncryptionKey: your-32-byte-encryption-key-here
      callbackURL: /oauth2/callback

Optional Parameters

Parameter Type Default Description
logoutURL string callbackURL + "/logout" Path for logout requests
postLogoutRedirectURI string / Redirect URL after logout
logLevel string info Logging verbosity (debug, info, error)
forceHTTPS bool true Force HTTPS for redirect URIs (set false only for plaintext HTTP local dev)
rateLimit int 100 Maximum requests per second
excludedURLs []string none Paths that bypass authentication
revocationURL string auto-discovered Token revocation endpoint
oidcEndSessionURL string auto-discovered Provider's end session endpoint
enablePKCE bool false Enable PKCE for authorization code flow
minimalHeaders bool false Reduce forwarded headers

TLS Termination at Load Balancer

forceHTTPS defaults to true, so redirect URIs always use https://. This is the correct default behind any TLS-terminating load balancer (AWS ALB, Google Cloud LB, Azure App Gateway) — X-Forwarded-Proto cannot be trusted (ALB may overwrite it).

Set forceHTTPS: false only when you serve OIDC over plaintext HTTP (local dev). Otherwise leave it at default.

Streaming Endpoints (SSE and WebSocket)

The middleware automatically bypasses the OIDC redirect for two request kinds that browsers cannot follow a 302 on:

Bypass Triggered by
Server-Sent Events (SSE) Accept: text/event-stream
WebSocket upgrade Upgrade: websocket + Connection: upgrade (RFC 6455)

These requests do not require any explicit configuration — they are handled implicitly. However, the bypass is not unauthenticated:

  • A valid, encrypted session cookie is required. Requests without one are rejected (the connection cannot proceed to the backend).
  • The session cookie is sealed with sessionEncryptionKey, so the authenticated flag cannot be forged.
  • Validation is cookie-only — no JWK fetch / signature verification — so streaming endpoints keep working when the OIDC provider is briefly unavailable.
  • The user identifier from the session is forwarded as X-Forwarded-User (and X-Auth-Request-User unless minimalHeaders: true).

For browser clients, the user must complete the normal OIDC flow on a regular HTTP page first; the resulting session cookie is then reused on the SSE / WebSocket connection.


Security Options

Audience Validation

Parameter Type Default Description
audience string clientID Expected audience for access token validation
strictAudienceValidation bool false Reject sessions with audience mismatch
allowOpaqueTokens bool false Enable opaque token support via RFC 7662
requireTokenIntrospection bool false Require introspection for opaque tokens

Production Security Configuration

audience: "https://my-api.example.com"
strictAudienceValidation: true

Opaque Token Support

allowOpaqueTokens: true
requireTokenIntrospection: true
strictAudienceValidation: true

Other Security Options

Parameter Type Default Description
disableReplayDetection bool false Disable JTI-based replay attack detection
allowPrivateIPAddresses bool false Allow private IPs in provider URLs

Session Management

Parameter Type Default Description
sessionMaxAge int 86400 (24h) Maximum session age in seconds
refreshGracePeriodSeconds int 60 Seconds before expiry to attempt refresh
maxRefreshTokenAgeSeconds int 21600 Heuristic max age (in seconds) of a stored refresh token. Once exceeded, requests treat the RT as expired up front (returns 401 to AJAX, triggers full re-auth on navigations) instead of grant-spamming the IdP with invalid_grant retries. IdPs do not advertise RT TTL on the wire, so this is intentionally a conservative heuristic — tune to match your provider. Set 0 to disable. Default 21600 (6h).
cookieDomain string auto-detected Domain for session cookies
cookiePrefix string _oidc_raczylo_ Prefix for cookie names

Multi-Subdomain Setup

cookieDomain: .example.com  # Share cookies across subdomains

Multiple Middleware Instances

When running multiple middleware instances with different authorization requirements, use unique prefixes:

# User authentication middleware
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: oidc-userauth
spec:
  plugin:
    traefikoidc:
      cookiePrefix: "_oidc_userauth_"
      sessionEncryptionKey: user-encryption-key-min-32-bytes
      # ... other config
---
# Admin authentication middleware
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: oidc-adminauth
spec:
  plugin:
    traefikoidc:
      cookiePrefix: "_oidc_adminauth_"
      sessionEncryptionKey: admin-encryption-key-min-32-bytes
      allowedUsers:
        - admin@example.com
      # ... other config

Extended Session Duration

sessionMaxAge: 604800  # 7 days
# Common values:
# 3600     - 1 hour (high security)
# 86400    - 1 day (default)
# 259200   - 3 days
# 604800   - 7 days
# 2592000  - 30 days

Access Control

User Restrictions

Parameter Type Description
allowedUserDomains []string Restrict to specific email domains
allowedUsers []string Specific email addresses allowed
allowedRolesAndGroups []string Required roles or groups
roleClaimName string JWT claim for roles (default: roles)
groupClaimName string JWT claim for groups (default: groups)
userIdentifierClaim string Claim for user ID (default: email)

Domain Restriction

allowedUserDomains:
  - company.com
  - subsidiary.com

Specific User Access

allowedUsers:
  - user@example.com
  - contractor@external.org

Role-Based Access Control

allowedRolesAndGroups:
  - admin
  - developer
roleClaimName: "https://myapp.com/roles"  # For namespaced claims (Auth0)

Access Control Logic

  • If only allowedUsers is set: Only specified emails can access
  • If only allowedUserDomains is set: Only specified domains can access
  • If both are set: Access granted if email is in allowedUsers OR domain is in allowedUserDomains
  • If neither is set: Any authenticated user can access

Users Without Email (Azure AD)

For Azure AD service accounts or users without email:

userIdentifierClaim: sub  # Options: sub, oid, upn, preferred_username
allowedUsers:
  - "abc12345-6789-0abc-def0-123456789abc"  # User object ID

Headers Configuration

Default Headers

The middleware sets these headers for downstream services:

Header Description
X-Forwarded-User User's email address
X-User-Groups Comma-separated user groups
X-User-Roles Comma-separated user roles
X-Auth-Request-Redirect Original request URI
X-Auth-Request-User User's email address
X-Auth-Request-Token User's ID token

Minimal Headers Mode

For "431 Request Header Fields Too Large" errors:

minimalHeaders: true  # Only forwards X-Forwarded-User

Custom Templated Headers

headers:
  - name: "X-User-Email"
    value: "{{{{.Claims.email}}}}"
  - name: "X-User-ID"
    value: "{{{{.Claims.sub}}}}"
  - name: "Authorization"
    value: "Bearer {{{{.AccessToken}}}}"
  - name: "X-User-Roles"
    value: "{{{{range $i, $e := .Claims.roles}}}}{{{{if $i}}}},{{{{end}}}}{{{{$e}}}}{{{{end}}}}"

Template Variables:

  • {{.Claims.field}} - ID token claims
  • {{.AccessToken}} - Raw access token
  • {{.IdToken}} - Raw ID token
  • {{.RefreshToken}} - Raw refresh token

Important: Use double curly braces ({{{{ and }}}}) to escape templates in YAML.


Security Headers

Security Profiles

Profile Use Case Security Level
default Standard web apps High
strict Maximum security Very High
development Local development Medium
api API endpoints High
custom Custom requirements Configurable

Basic Configuration

securityHeaders:
  enabled: true
  profile: "default"

API with CORS

securityHeaders:
  enabled: true
  profile: "api"
  corsEnabled: true
  corsAllowedOrigins:
    - "https://your-frontend.com"
    - "https://*.example.com"
  corsAllowCredentials: true

Custom Security Configuration

securityHeaders:
  enabled: true
  profile: "custom"

  # Content Security Policy
  contentSecurityPolicy: "default-src 'self'; script-src 'self'"

  # HSTS
  strictTransportSecurity: true
  strictTransportSecurityMaxAge: 31536000
  strictTransportSecuritySubdomains: true
  strictTransportSecurityPreload: true

  # Frame and Content Protection
  frameOptions: "DENY"
  contentTypeOptions: "nosniff"
  xssProtection: "1; mode=block"
  referrerPolicy: "strict-origin-when-cross-origin"

  # CORS
  corsEnabled: true
  corsAllowedOrigins: ["https://app.example.com"]
  corsAllowedMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
  corsAllowedHeaders: ["Authorization", "Content-Type"]
  corsAllowCredentials: true
  corsMaxAge: 86400

  # Custom Headers
  customHeaders:
    X-Custom-Header: "value"

  # Server Identification
  disableServerHeader: true
  disablePoweredByHeader: true

CORS Origin Patterns

corsAllowedOrigins:
  - "https://example.com"        # Exact match
  - "https://*.example.com"      # Subdomain wildcard
  - "http://localhost:*"         # Port wildcard (development)

Scope Configuration

Default Behavior (Append Mode)

scopes:
  - roles
  - custom_scope
# Result: ["openid", "profile", "email", "roles", "custom_scope"]

Override Mode

overrideScopes: true
scopes:
  - openid
  - profile
  - custom_scope
# Result: ["openid", "profile", "custom_scope"]

Advanced Options

Dynamic Client Registration (RFC 7591)

Dynamic Client Registration allows the middleware to automatically register itself with the OIDC provider, eliminating the need to manually create client credentials.

Basic Configuration (Single Instance):

dynamicClientRegistration:
  enabled: true
  initialAccessToken: "your-token"  # Optional, if provider requires it
  persistCredentials: true
  credentialsFile: "/tmp/oidc-credentials.json"
  clientMetadata:
    redirect_uris:
      - "https://your-app.com/oauth2/callback"
    client_name: "My Application"
    application_type: "web"
    grant_types:
      - "authorization_code"
      - "refresh_token"

Multi-Replica Deployment (Kubernetes):

For Kubernetes deployments with multiple replicas, use Redis storage to share credentials across all instances and prevent registration race conditions:

dynamicClientRegistration:
  enabled: true
  persistCredentials: true
  storageBackend: "redis"  # Share credentials via Redis
  redisKeyPrefix: "myapp:dcr:"  # Optional custom prefix
  clientMetadata:
    redirect_uris:
      - "https://your-app.com/oauth2/callback"
    client_name: "My Application"

redis:
  enabled: true
  address: "redis:6379"
  cacheMode: "redis"

Storage Backend Options:

Backend Description Use Case
file Store credentials in local file Single instance deployments
redis Store credentials in Redis Multi-replica Kubernetes deployments
auto Use Redis if available, fallback to file Flexible deployments (default)

Multi-Replica Deployment

Without Redis, disable replay detection:

disableReplayDetection: true

With Redis (recommended):

redis:
  enabled: true
  address: "redis:6379"
  cacheMode: "hybrid"

See REDIS.md for complete Redis configuration.


Kubernetes Secrets

Reference secrets instead of hardcoding sensitive values:

providerURL: urn:k8s:secret:oidc-secret:ISSUER
clientID: urn:k8s:secret:oidc-secret:CLIENT_ID
clientSecret: urn:k8s:secret:oidc-secret:SECRET

Create the secret:

kubectl create secret generic oidc-secret \
  --from-literal=ISSUER=https://accounts.google.com \
  --from-literal=CLIENT_ID=your-client-id \
  --from-literal=SECRET=your-client-secret \
  -n traefik

Environment Variable Naming

Important: Avoid using "API" as a substring in environment variable names when using ${VAR} syntax in Traefik configuration. Traefik reserves TRAEFIK_API_* variables and the substring may cause conflicts.

# Bad - may cause issues
sessionEncryptionKey: ${OIDC_SECRET_API}

# Good
sessionEncryptionKey: ${OIDC_SECRET_SVC}