This middleware replaces the need for forward-auth and oauth2-proxy when using Traefik as a reverse proxy to support OpenID Connect (OIDC) authentication.
The Traefik OIDC middleware provides a complete OIDC authentication solution with these key features:
| Provider | Support Level | Refresh Tokens | Auto-Detection | Key Features |
|---|---|---|---|---|
| ✅ Full OIDC | ✅ Yes | ✅ accounts.google.com | Auto-config, Workspace support | |
| Azure AD | ✅ Full OIDC | ✅ Yes | ✅ login.microsoftonline.com | Multi-tenant, group claims |
| Auth0 | ✅ Full OIDC | ✅ Yes | ✅ *.auth0.com | Custom claims, flexible rules |
| Okta | ✅ Full OIDC | ✅ Yes | ✅ *.okta.com | Enterprise SSO, MFA support |
| Keycloak | ✅ Full OIDC | ✅ Yes | ✅ /auth/realms/ path | Self-hosted, full customization |
| AWS Cognito | ✅ Full OIDC | ✅ Yes | ✅ cognito-idp.*.amazonaws.com | Managed service, regional |
| GitLab | ✅ Full OIDC | ✅ Yes | ✅ gitlab.com | Self-hosted support |
| GitHub | ⚠️ OAuth 2.0 Only | ❌ No | ✅ github.com | API access only, no claims |
| Generic OIDC | ✅ Full OIDC | ✅ Yes | ✅ Any endpoint | RFC-compliant providers |
| Feature | Azure AD | Auth0 | Okta | Keycloak | Cognito | GitLab | GitHub | Generic | |
|---|---|---|---|---|---|---|---|---|---|
| ID Tokens | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Refresh Tokens | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Auto-Configuration | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Custom Claims | Limited | ✅ | ✅ | ✅ | ✅ | ✅ | Limited | ❌ | Varies |
| Group/Role Claims | Limited | ✅ | ✅ | ✅ | ✅ | ✅ | Limited | ❌ | Varies |
| Domain Restriction | ✅ (hd claim) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | Varies |
| Self-Hosted | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ |
| Enterprise Features | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | Varies |
Important: GitHub uses OAuth 2.0 (not OpenID Connect) and only provides access tokens. Use it for API access only, not for user authentication with claims. All other providers support full OIDC with ID tokens and user claims.
Important Note on Token Validation: This middleware performs authentication and claim extraction based on the ID Token provided by the OIDC provider. It does not primarily use the Access Token for these purposes (though the Access Token is available for templated headers if needed). Therefore, ensure that all necessary claims (e.g., email, roles, custom attributes) are included in the ID Token by your OIDC provider's configuration.
The middleware has been tested with Google, Azure AD, Auth0, Okta, Keycloak, AWS Cognito, GitLab, GitHub (OAuth 2.0), and other standard OIDC providers. It includes automatic provider detection and special handling for provider-specific requirements.
This middleware includes advanced memory management features to ensure stable operation under high load:
This middleware follows closely the current Traefik helm chart versions. If the plugin fails to load, it's time to update to the latest version of the Traefik helm chart.
# traefik.ymlexperimental:plugins:traefikoidc:moduleName: github.com/lukaszraczylo/traefikoidcversion: v0.7.10 # Use the latest version
For local development or testing, you can use the provided Docker Compose setup:
cd dockerdocker-compose up -d
This will start Traefik with the OIDC middleware and two test services.
The middleware supports the following configuration options:
| Parameter | Description | Example |
|---|---|---|
providerURL | The base URL of the OIDC provider | https://accounts.google.com |
clientID | The OAuth 2.0 client identifier | 1234567890.apps.googleusercontent.com |
clientSecret | The OAuth 2.0 client secret | your-client-secret |
sessionEncryptionKey | Key used to encrypt session data (must be at least 32 bytes long) | potato-secret-is-at-least-32-bytes-long |
callbackURL | The path where the OIDC provider will redirect after authentication | /oauth2/callback |
| Parameter | Description | Default | Example |
|---|---|---|---|
logoutURL | The path for handling logout requests | callbackURL + "/logout" | /oauth2/logout |
postLogoutRedirectURI | The URL to redirect to after logout | / | /logged-out-page |
scopes | OAuth 2.0 scopes to use for authentication | ["openid", "profile", "email"] (always included by default) | ["roles", "custom_scope"] (appended to defaults) |
overrideScopes | When true, replaces default scopes with provided scopes instead of appending | false | true (use only the scopes explicitly provided) |
logLevel | Sets the logging verbosity | info | debug, info, error |
forceHTTPS | Forces HTTPS scheme for redirect URIs (REQUIRED for TLS termination at load balancer like AWS ALB) | false (when not specified) | true, false |
rateLimit | Sets the maximum number of requests per second | 100 | 500 |
excludedURLs | Lists paths that bypass authentication | none | ["/health", "/metrics", "/public"] |
allowedUserDomains | Restricts access to specific email domains | none | ["company.com", "subsidiary.com"] |
allowedUsers | A list of specific email addresses that are allowed access | none | ["user1@example.com", "user2@another.org"] |
allowedRolesAndGroups | Restricts access to users with specific roles or groups | none | ["admin", "developer"] |
roleClaimName | JWT claim name for extracting user roles (supports namespaced claims for Auth0) | "roles" | "https://myapp.com/roles", "user_roles" |
groupClaimName | JWT claim name for extracting user groups (supports namespaced claims for Auth0) | "groups" | "https://myapp.com/groups", "user_groups" |
userIdentifierClaim | JWT claim to use as user identifier (for users without email, e.g., Azure AD service accounts) | "email" | "sub", "oid", "upn", "preferred_username" |
revocationURL | The endpoint for revoking tokens | auto-discovered | https://accounts.google.com/revoke |
oidcEndSessionURL | The provider's end session endpoint | auto-discovered | https://accounts.google.com/logout |
enablePKCE | Enables PKCE (Proof Key for Code Exchange) for authorization code flow | false | true, false |
refreshGracePeriodSeconds | Seconds before token expiry to attempt proactive refresh | 60 | 120 |
cookieDomain | Explicit domain for session cookies (important for multi-subdomain setups) | auto-detected | .example.com, app.example.com |
cookiePrefix | Custom prefix for session cookie names (for isolating multiple middleware instances) | _oidc_raczylo_ | _oidc_userauth_, _oidc_admin_ |
sessionMaxAge | Maximum session age in seconds before requiring re-authentication | 86400 (24 hours) | 3600 (1 hour), 604800 (7 days) |
audience | Custom audience for access token validation (for Auth0 custom APIs, etc.) | clientID | https://my-api.example.com |
strictAudienceValidation | Reject sessions with access token audience mismatch (prevents token confusion attacks) | false | true |
allowOpaqueTokens | Enable opaque (non-JWT) access token support via RFC 7662 introspection | false | true |
requireTokenIntrospection | Require introspection for opaque tokens (force validation, no fallback) | false | true |
headers | Custom HTTP headers with templates that can access OIDC claims and tokens | none | See "Templated Headers" section |
securityHeaders | Configure security headers including CSP, HSTS, CORS, and custom headers | enabled with default profile | See "Security Headers Configuration" section |
disableReplayDetection | Disable JTI-based replay attack detection for multi-replica deployments | false | true |
allowPrivateIPAddresses | Allow private IP addresses in provider URLs (for internal networks with Keycloak, etc.) | false | true |
minimalHeaders | Reduce forwarded headers to prevent "431 Request Header Fields Too Large" errors | false | true |
redis | Redis cache configuration for distributed deployments | disabled | See "Redis Cache" section |
⚠️ IMPORTANT - TLS Termination at Load Balancer:
If you're running Traefik behind a load balancer (AWS ALB, Google Cloud Load Balancer, Azure Application Gateway, etc.) that terminates TLS:
- You MUST set
forceHTTPS: truein your configuration- Without this setting, redirect URIs will use
http://instead ofhttps://, causing OAuth callback failures- This is especially critical for AWS ALB which may overwrite the
X-Forwarded-ProtoheaderDefault behavior:
- When
forceHTTPSis not specified in your config → defaults tofalse(Go zero value)- When
forceHTTPS: trueis explicitly set → always useshttps://for redirect URIs- When
forceHTTPS: falseis explicitly set → scheme detection based on headers/TLSSee GitHub Issue #82 for details.
The middleware supports two modes for handling OAuth 2.0 scopes, controlled by the overrideScopes parameter:
overrideScopes: false)By default, the middleware uses an append behavior for OAuth 2.0 scopes:
["openid", "profile", "email"]overrideScopes: true)When overrideScopes is set to true, the middleware uses replacement behavior:
scopes field are usedopenid if neededDefault behavior (no custom scopes):
# No scopes field specified# Result: ["openid", "profile", "email"]
Default append behavior:
scopes:- roles- custom_scope# Result: ["openid", "profile", "email", "roles", "custom_scope"]
Overlapping scopes with append (automatic deduplication):
scopes:- openid # Duplicate - will be deduplicated- roles- profile # Duplicate - will be deduplicated- permissions# Result: ["openid", "profile", "email", "roles", "permissions"]
Using override mode:
overrideScopes: truescopes:- openid- profile- custom_scope# Result: ["openid", "profile", "custom_scope"]
Empty scopes list with default behavior:
scopes: []# Result: ["openid", "profile", "email"]
Empty scopes list with override mode:
overrideScopes: truescopes: []# Result: [] (Warning: empty scopes may cause authentication to fail)
The default append behavior ensures essential OIDC scopes are always present, while the override mode gives you complete control over the exact scopes requested from the provider.
The middleware provides comprehensive support for Auth0 audience validation to prevent token confusion attacks. Auth0 can issue tokens in three different scenarios, each requiring specific configuration.
Per OAuth 2.0 and OIDC specifications:
aud = client_id (OIDC Core 1.0 spec)Proper audience validation prevents token confusion attacks where a token intended for one API is used to access another API.
Configuration:
audience: "https://my-api.example.com" # Your API identifier from Auth0strictAudienceValidation: true # Enforce strict validation
Result: Fully secure, OIDC compliant with proper access token audience validation.
Configuration:
# audience not specified (defaults to client_id)strictAudienceValidation: true # Recommended: reject mismatched tokens
Behavior: Access tokens may not contain client_id in audience, triggering security warnings. Set strictAudienceValidation: true to reject such sessions.
Configuration:
allowOpaqueTokens: true # Enable opaque token supportrequireTokenIntrospection: true # Require introspection (recommended)
Result: Secure with OAuth 2.0 Token Introspection (RFC 7662).
| Option | Purpose | Recommended Value |
|---|---|---|
audience | Expected audience for access tokens | Your API identifier or leave empty |
strictAudienceValidation | Reject sessions with audience mismatch | true for production |
allowOpaqueTokens | Accept non-JWT access tokens | true if provider issues opaque tokens |
requireTokenIntrospection | Force introspection for opaque tokens | true when allowOpaqueTokens=true |
Production Configuration (Scenario 1):
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-auth0-securespec:plugin:traefikoidc:providerURL: https://your-auth0-domain.auth0.comclientID: your-auth0-client-idclientSecret: your-auth0-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbackaudience: "https://my-api.example.com"strictAudienceValidation: trueallowedRolesAndGroups:- "https://your-app.com/roles:admin"- editor
Opaque Token Configuration (Scenario 3):
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-auth0-opaquespec:plugin:traefikoidc:providerURL: https://your-auth0-domain.auth0.comclientID: your-auth0-client-idclientSecret: your-auth0-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbackallowOpaqueTokens: truerequireTokenIntrospection: truestrictAudienceValidation: true
For detailed Auth0 configuration including all three scenarios, troubleshooting, and security best practices, see AUTH0_AUDIENCE_GUIDE.md.
The middleware includes comprehensive security headers support to protect your applications against common web vulnerabilities. Security headers are applied to all authenticated responses.
Choose from predefined security profiles or create custom configurations:
| Profile | Use Case | Security Level | CORS Enabled |
|---|---|---|---|
default | Standard web applications | High | Disabled |
strict | Maximum security applications | Very High | Disabled |
development | Local development | Medium | Enabled (localhost) |
api | API endpoints | High | Configurable |
custom | Custom requirements | Configurable | Configurable |
securityHeaders:enabled: trueprofile: "default"
securityHeaders:enabled: trueprofile: "strict"
securityHeaders:enabled: trueprofile: "api"corsEnabled: truecorsAllowedOrigins:- "https://your-frontend.com"- "https://*.example.com"corsAllowCredentials: true
securityHeaders:enabled: trueprofile: "custom"# Content Security PolicycontentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'"# HSTS SettingsstrictTransportSecurity: truestrictTransportSecurityMaxAge: 31536000 # 1 yearstrictTransportSecuritySubdomains: truestrictTransportSecurityPreload: true# Frame and Content ProtectionframeOptions: "DENY"contentTypeOptions: "nosniff"xssProtection: "1; mode=block"referrerPolicy: "strict-origin-when-cross-origin"# CORS ConfigurationcorsEnabled: truecorsAllowedOrigins: ["https://app.example.com"]corsAllowedMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]corsAllowedHeaders: ["Authorization", "Content-Type", "X-Requested-With"]corsAllowCredentials: truecorsMaxAge: 86400# Custom HeaderscustomHeaders:X-Custom-Header: "custom-value"X-API-Version: "v1"# Server IdentificationdisableServerHeader: truedisablePoweredByHeader: true
| Parameter | Description | Default | Example |
|---|---|---|---|
enabled | Enable/disable security headers | true | true, false |
profile | Security profile to use | default | default, strict, development, api, custom |
contentSecurityPolicy | CSP header value | Profile-based | "default-src 'self'" |
strictTransportSecurity | Enable HSTS | true | true, false |
strictTransportSecurityMaxAge | HSTS max age in seconds | 31536000 | 86400 |
strictTransportSecuritySubdomains | Include subdomains in HSTS | true | true, false |
strictTransportSecurityPreload | Enable HSTS preload | true | true, false |
frameOptions | X-Frame-Options header | DENY | DENY, SAMEORIGIN, ALLOW-FROM uri |
contentTypeOptions | X-Content-Type-Options header | nosniff | nosniff |
xssProtection | X-XSS-Protection header | 1; mode=block | 1; mode=block |
referrerPolicy | Referrer-Policy header | strict-origin-when-cross-origin | no-referrer |
corsEnabled | Enable CORS headers | false | true, false |
corsAllowedOrigins | Allowed CORS origins | [] | ["https://app.com", "https://*.example.com"] |
corsAllowedMethods | Allowed CORS methods | ["GET", "POST", "OPTIONS"] | ["GET", "POST", "PUT", "DELETE"] |
corsAllowedHeaders | Allowed CORS headers | ["Authorization", "Content-Type"] | ["X-Custom-Header"] |
corsAllowCredentials | Allow credentials in CORS | false | true, false |
corsMaxAge | CORS preflight cache time | 86400 | 3600 |
customHeaders | Additional custom headers | {} | {"X-Custom": "value"} |
disableServerHeader | Remove Server header | true | true, false |
disablePoweredByHeader | Remove X-Powered-By header | true | true, false |
permissionsPolicy | Permissions-Policy header | `` | "geolocation=(), camera=(), microphone=()" |
crossOriginEmbedderPolicy | Cross-Origin-Embedder-Policy header | `` | "require-corp", "credentialless", "unsafe-none" |
crossOriginOpenerPolicy | Cross-Origin-Opener-Policy header | `` | "same-origin", "same-origin-allow-popups", "unsafe-none" |
crossOriginResourcePolicy | Cross-Origin-Resource-Policy header | `` | "same-origin", "same-site", "cross-origin" |
The middleware supports flexible CORS origin patterns:
corsAllowedOrigins:- "https://example.com" # Exact match- "https://*.example.com" # Subdomain wildcard- "http://localhost:*" # Port wildcard (development)- "*" # Allow all (not recommended)
The middleware provides several advanced configuration options for production environments.
The middleware automatically optimizes for each OIDC provider:
access_type=offline and prompt=consent for refresh tokens# Optimized for high-traffic environmentsrateLimit: 1000refreshGracePeriodSeconds: 300securityHeaders:enabled: trueprofile: "api"corsEnabled: truecorsMaxAge: 86400
# Maximum security for sensitive environmentsrateLimit: 50allowedUserDomains: ["company.com"]allowedRolesAndGroups: ["admin", "developer"]securityHeaders:enabled: trueprofile: "strict"corsEnabled: false
# Development-friendly settingslogLevel: "debug"forceHTTPS: falsesecurityHeaders:enabled: trueprofile: "development"corsEnabled: truecorsAllowedOrigins: ["http://localhost:*"]
When running multiple Traefik replicas with the OIDC plugin, you may encounter false positive replay detection errors. Each replica maintains its own in-memory JTI (JWT Token ID) cache, causing legitimate token reuse to be flagged as replay attacks.
Problem: When the same valid token hits different replicas:
Solution 1 (Simple): Disable replay detection for distributed deployments:
disableReplayDetection: true # Disable JTI replay detection for multi-replica setups
Solution 2 (Recommended): Use Redis cache backend for shared state (see Redis Cache section)
Security Note: When disableReplayDetection: true:
Example Configuration:
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-multi-replicanamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: your-client-idclientSecret: your-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbackdisableReplayDetection: true # Required for multi-replica deployments without Redis
Recommendation: For single-instance deployments, leave this setting at false (default) to maintain replay attack protection. For multi-replica deployments, use the Redis cache backend for proper replay detection across all instances.
The plugin supports optional Redis caching for multi-replica deployments. This solves issues with JTI replay detection and session management when running multiple Traefik instances behind a load balancer.
✨ Yaegi Compatible: Redis support is implemented using a pure-Go RESP protocol client that works seamlessly with Traefik's Yaegi interpreter (no
unsafepackage). Full Redis functionality is available for both dynamic plugin loading and pre-compiled deployments.
When running multiple Traefik replicas, each instance maintains its own in-memory cache for:
Without a shared cache, you may experience:
Redis is configured through Traefik's dynamic configuration (YAML, labels, etc.):
# Enable Redis cache in your middleware configurationredis:enabled: trueaddress: "localhost:6379"password: "your-password" # Optionaldb: 0keyPrefix: "traefikoidc:"
The plugin uses the following priority for Redis configuration:
This approach allows you to manage all settings through Traefik's configuration system while maintaining backward compatibility with environment variables.
| Parameter | Description | Default | Example |
|---|---|---|---|
enabled | Enable Redis caching | false | true |
address | Redis server address | - | redis:6379 |
password | Redis password | - | YOUR_PASSWORD |
db | Database number | 0 | 1 |
keyPrefix | Key prefix for namespacing | traefikoidc: | myapp: |
cacheMode | Cache mode: redis, hybrid, memory | redis | hybrid |
poolSize | Connection pool size | 10 | 20 |
connectTimeout | Connection timeout (seconds) | 5 | 10 |
readTimeout | Read timeout (seconds) | 3 | 5 |
writeTimeout | Write timeout (seconds) | 3 | 5 |
enableTLS | Enable TLS | false | true |
tlsSkipVerify | Skip TLS verification | false | true |
enableCircuitBreaker | Circuit breaker for failures | true | true |
circuitBreakerThreshold | Failures before circuit opens | 5 | 10 |
circuitBreakerTimeout | Circuit reset timeout (seconds) | 60 | 30 |
enableHealthCheck | Periodic health checks | true | true |
healthCheckInterval | Health check interval (seconds) | 30 | 60 |
If not configured through Traefik, these environment variables can be used as fallback:
REDIS_ENABLED - Enable Redis cacheREDIS_ADDRESS - Redis server addressREDIS_PASSWORD - Redis passwordREDIS_DB - Database numberREDIS_KEY_PREFIX - Key prefixREDIS_CACHE_MODE - Cache modeREDIS_POOL_SIZE - Connection pool sizeREDIS_CONNECT_TIMEOUT - Connection timeoutREDIS_READ_TIMEOUT - Read timeoutREDIS_WRITE_TIMEOUT - Write timeoutREDIS_ENABLE_TLS - Enable TLSREDIS_TLS_SKIP_VERIFY - Skip TLS verificationThe plugin supports three cache modes:
services:redis:image: redis:alpinecommand: redis-server --requirepass yourpasswordtraefik:image: traefik:v3.2# ... rest of your Traefik configurationlabels:# Configure the OIDC middleware with Redis- "traefik.http.middlewares.oidc.plugin.traefikoidc.clientID=your-client-id"- "traefik.http.middlewares.oidc.plugin.traefikoidc.clientSecret=your-secret"- "traefik.http.middlewares.oidc.plugin.traefikoidc.providerURL=https://auth.example.com"- "traefik.http.middlewares.oidc.plugin.traefikoidc.callbackURL=/oauth2/callback"- "traefik.http.middlewares.oidc.plugin.traefikoidc.sessionEncryptionKey=your-64-char-key"# Redis configuration via labels- "traefik.http.middlewares.oidc.plugin.traefikoidc.redis.enabled=true"- "traefik.http.middlewares.oidc.plugin.traefikoidc.redis.address=redis:6379"- "traefik.http.middlewares.oidc.plugin.traefikoidc.redis.password=yourpassword"- "traefik.http.middlewares.oidc.plugin.traefikoidc.redis.cacheMode=hybrid"
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-with-redisspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: your-client-idclientSecret: your-client-secretsessionEncryptionKey: your-encryption-keycallbackURL: /oauth2/callbackredis:enabled: trueaddress: "redis-service.redis-namespace:6379"password: "urn:k8s:secret:redis-secret:password"db: 0keyPrefix: "traefikoidc"cacheMode: "hybrid"
See Redis Cache Documentation for:
The middleware supports OIDC Dynamic Client Registration (RFC 7591), allowing automatic client registration with OIDC providers without manual pre-registration. This is useful for:
registration_endpoint from the provider's .well-known/openid-configurationclientID is configured, it automatically registers a new client with the providerclient_id and client_secret are cached and optionally persisted to a fileapiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-dynamic-registrationnamespace: traefikspec:plugin:traefikoidc:providerURL: https://your-oidc-provider.com# clientID and clientSecret are NOT required when using DCRsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbackdynamicClientRegistration:enabled: true# Optional: Initial access token for protected registration endpointsinitialAccessToken: "your-initial-access-token"# Optional: Override the registration endpoint (auto-discovered by default)registrationEndpoint: "https://your-provider.com/register"# Optional: Persist credentials to file for reuse across restartspersistCredentials: truecredentialsFile: "/tmp/oidc-client-credentials.json"# Client metadata for registrationclientMetadata:redirect_uris:- "https://your-app.com/oauth2/callback"client_name: "My Application"application_type: "web"grant_types:- "authorization_code"- "refresh_token"response_types:- "code"token_endpoint_auth_method: "client_secret_basic"contacts:- "admin@your-app.com"
| Parameter | Description | Required | Default |
|---|---|---|---|
enabled | Enable dynamic client registration | Yes | false |
initialAccessToken | Bearer token for protected registration endpoints | No | - |
registrationEndpoint | Override auto-discovered registration endpoint | No | From discovery |
persistCredentials | Save registered credentials to file | No | false |
credentialsFile | Path to store/load credentials | No | /tmp/oidc-client-credentials.json |
clientMetadata.redirect_uris | REQUIRED - Redirect URIs for OAuth flow | Yes | - |
clientMetadata.client_name | Human-readable client name | No | - |
clientMetadata.application_type | web or native | No | web |
clientMetadata.grant_types | OAuth grant types | No | ["authorization_code", "refresh_token"] |
clientMetadata.response_types | OAuth response types | No | ["code"] |
clientMetadata.token_endpoint_auth_method | Authentication method | No | client_secret_basic |
clientMetadata.contacts | Contact email addresses | No | - |
clientMetadata.logo_uri | URL to client logo | No | - |
clientMetadata.client_uri | URL to client homepage | No | - |
clientMetadata.policy_uri | URL to privacy policy | No | - |
clientMetadata.tos_uri | URL to terms of service | No | - |
clientMetadata.scope | Space-separated scopes | No | - |
DCR support varies by provider:
| Provider | DCR Support | Notes |
|---|---|---|
| Keycloak | ✅ Full | Enable in realm settings |
| Auth0 | ✅ Full | Requires Management API token |
| Okta | ✅ Full | Enable Dynamic Client Registration |
| Azure AD | ⚠️ Limited | App Registration API instead |
| ❌ No | Manual registration required | |
| AWS Cognito | ❌ No | Manual registration required |
client_secret_expires_at and handle rotation if neededdynamicClientRegistration:enabled: trueclientMetadata:redirect_uris:- "https://myapp.example.com/oauth2/callback"client_name: "My App - Production"application_type: "web"grant_types:- "authorization_code"- "refresh_token"
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-basicnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-with-open-urlsnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]excludedURLs:- /login # covers /login, /login/me, /login/reminder etc.- /public-data- /health- /metrics
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-domain-restrictednamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]allowedUserDomains:- company.com- subsidiary.com
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-specific-usersnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]allowedUsers:- user1@example.com- user2@another.org
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-domain-and-usersnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]allowedUserDomains:- company.comallowedUsers:- special-user@gmail.com- contractor@external.org
When configuring access control:
allowedUsers is set, only the specified email addresses will be granted accessallowedUserDomains is set, only users with email addresses from those domains will be granted accessallowedUsers OR their email's domain is in allowedUserDomainsapiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-rbacnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]allowedRolesAndGroups:- admin- developer
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-multi-subdomainnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutcookieDomain: .example.com # Allows cookies to be shared across all subdomainsscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
Important: The cookieDomain parameter is crucial when running behind a reverse proxy or when your application serves multiple subdomains. Without it, cookies may be created with inconsistent domains, leading to authentication issues like "CSRF token missing in session" errors.
When running multiple middleware instances with different authorization requirements (e.g., one for general users and one for admins), you must use different cookiePrefix values to prevent session sharing between instances:
# Middleware for general user authenticationapiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-userauthnamespace: traefikspec:plugin:traefikoidc:providerURL: https://auth.example.comclientID: your-client-idclientSecret: your-client-secretsessionEncryptionKey: user-key-at-least-32-bytes-longcallbackURL: /oauth2/callbackcookiePrefix: "_oidc_userauth_" # Unique prefix for this instance---# Middleware for admin authentication with stricter requirementsapiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-adminauthnamespace: traefikspec:plugin:traefikoidc:providerURL: https://auth.example.comclientID: your-client-idclientSecret: your-client-secretsessionEncryptionKey: admin-key-at-least-32-bytes-long # Different encryption keycallbackURL: /oauth2/admin/callback # Different callback URLcookiePrefix: "_oidc_adminauth_" # Different prefix for isolationallowedUsers: # Restricted to specific admin users- admin@example.com- superadmin@example.com
Security Note: When running multiple instances, ensure you use:
cookiePrefix values to prevent cookie name collisionssessionEncryptionKey values for complete session isolationcallbackURL paths to avoid routing conflictsThis configuration prevents authorization bypass issues where a user authenticated via the general middleware could access admin-protected routes. See issue #87 for more details.
For applications that users access infrequently (weekly or monthly), you can extend the session duration beyond the default 24 hours to reduce authentication friction:
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-long-sessionnamespace: traefikspec:plugin:traefikoidc:providerURL: https://auth.example.comclientID: your-client-idclientSecret: your-client-secretsessionEncryptionKey: your-key-at-least-32-bytes-longcallbackURL: /oauth2/callbacksessionMaxAge: 604800 # 7 days (in seconds)# Other common values:# 259200 - 3 days# 604800 - 7 days# 1209600 - 14 days# 2592000 - 30 days
Security Note: Longer session durations improve user experience but increase security risk. Consider your application's security requirements:
See issue #91 for more details.
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-custom-settingsnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutlogLevel: debug # Options: debug, info, error (default: info)rateLimit: 500 # Requests per second (default: 100)forceHTTPS: false # Default is true for securityscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-custom-logoutnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutpostLogoutRedirectURI: /logged-out-page # Where to redirect after logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-with-headersnamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]headers:# Using double curly braces to escape template expressions- 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}}}}"- name: "X-Is-Admin"value: "{{{{if eq .Claims.role \"admin\"}}}}true{{{{else}}}}false{{{{end}}}}"
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-with-pkcenamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutenablePKCE: true # Enables PKCE for added securityscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-googlenamespace: traefikspec:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: your-google-client-id.apps.googleusercontent.comclientSecret: your-google-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]# Note: DO NOT manually add offline_access scope for Google# The middleware automatically handles Google-specific requirementsrefreshGracePeriodSeconds: 300 # Optional: Start refresh 5 min before expiryallowedUserDomains:- your-gsuite-domain.com # Optional: Restrict to workspace users
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-azurenamespace: traefikspec:plugin:traefikoidc:providerURL: https://login.microsoftonline.com/your-tenant-id/v2.0clientID: your-azure-ad-client-idclientSecret: your-azure-ad-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # For group/role claims, configure in Azure AD Token ConfigurationallowedUserDomains:- yourcompany.comallowedRolesAndGroups:- "group-object-id-1" # Azure AD group Object IDs- "AppRoleName" # Application role names
For Azure AD users without email addresses (service accounts, organizational accounts without mail attributes):
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-azure-no-emailnamespace: traefikspec:plugin:traefikoidc:providerURL: https://login.microsoftonline.com/your-tenant-id/v2.0clientID: your-azure-ad-client-idclientSecret: your-azure-ad-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logout# Use 'sub' instead of 'email' for user identificationuserIdentifierClaim: sub # Can also use: "oid", "upn", "preferred_username"overrideScopes: true # Optional: Don't request email scope if not neededscopes:- openid- profile- groups# When using non-email identifiers, allowedUsers matches against the claim valueallowedUsers:- "abc12345-6789-0abc-def0-123456789abc" # Azure AD user object ID- "def67890-1234-5678-90ab-cdef12345678"# NOTE: allowedUserDomains is ignored when userIdentifierClaim is not "email"
Note: When
userIdentifierClaimis set to a non-email claim (likesub,oid, orupn), theallowedUserDomainsconfiguration is ignored since domain-based validation only applies to email addresses. UseallowedUserswith the actual claim values instead.
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-auth0namespace: traefikspec:plugin:traefikoidc:providerURL: https://your-auth0-domain.auth0.comclientID: your-auth0-client-idclientSecret: your-auth0-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logout# Audience configuration for custom APIsaudience: "https://my-api.example.com" # Your API identifier from Auth0strictAudienceValidation: true # Enforce proper audience validationscopes:- read:custom_data # Custom scopes as needed# Custom claim names for Auth0 namespaced claimsroleClaimName: "https://your-app.com/roles" # Auth0 requires namespaced custom claimsgroupClaimName: "https://your-app.com/groups" # Must match claims added in Auth0 ActionsallowedRolesAndGroups:- admin # Will match "admin" in https://your-app.com/roles claim- editorpostLogoutRedirectURI: /logged-out-page # Must be in Auth0 Allowed Logout URLs
Note: For detailed Auth0 audience configuration including opaque tokens and all security scenarios, see AUTH0_AUDIENCE_GUIDE.md.
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-oktanamespace: traefikspec:plugin:traefikoidc:providerURL: https://your-tenant.okta.com/oauth2/defaultclientID: your-okta-client-idclientSecret: your-okta-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- groups # Include groups in token claimsallowedRolesAndGroups:- admin- developer- "Everyone" # Default Okta group
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-keycloaknamespace: traefikspec:plugin:traefikoidc:providerURL: https://your-keycloak-domain/auth/realms/your-realmclientID: your-keycloak-client-idclientSecret: your-keycloak-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles- groupsallowedRolesAndGroups:- admin- editor# Ensure Keycloak client mappers add necessary claims to ID Token# For internal Keycloak deployments with private IPs (e.g., Docker network):# allowPrivateIPAddresses: true
Internal Network Deployment: If your Keycloak runs on an internal network with private IP addresses (e.g.,
192.168.x.x,10.x.x.x,172.16-31.x.x) and you don't have DNS resolution available, setallowPrivateIPAddresses: trueto allow the plugin to connect to your Keycloak instance. See Issue #97 for details.
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-cognitonamespace: traefikspec:plugin:traefikoidc:providerURL: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_YourUserPoolclientID: your-cognito-client-idclientSecret: your-cognito-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- aws.cognito.signin.user.admin # Cognito-specific scopeallowedRolesAndGroups:- admin- user
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-gitlabnamespace: traefikspec:plugin:traefikoidc:providerURL: https://gitlab.comclientID: your-gitlab-client-idclientSecret: your-gitlab-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- read_user- read_apiallowedUserDomains:- yourcompany.com
Warning: GitHub uses OAuth 2.0, not OpenID Connect. Use only for API access, not user authentication.
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oauth-githubnamespace: traefikspec:plugin:traefikoidc:providerURL: https://github.com/login/oauthclientID: your-github-client-idclientSecret: your-github-client-secretsessionEncryptionKey: your-secure-encryption-key-min-32-charscallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- user:email- read:user# Note: No ID tokens available, only access tokens for GitHub API# No refresh tokens - users must re-authenticate when tokens expire
The middleware automatically detects each provider and applies the necessary adjustments to ensure proper authentication and token refresh.
For Kubernetes environments, you can reference secrets instead of hardcoding sensitive values:
apiVersion: traefik.io/v1alpha1kind: Middlewaremetadata:name: oidc-with-secretsnamespace: traefikspec:plugin:traefikoidc:providerURL: urn:k8s:secret:traefik-middleware-oidc:ISSUERclientID: urn:k8s:secret:traefik-middleware-oidc:CLIENT_IDclientSecret: urn:k8s:secret:traefik-middleware-oidc:SECRETsessionEncryptionKey: potato-secret-is-at-least-32-bytes-longcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]
Don't forget to create the secret:
kubectl create secret generic traefik-middleware-oidc \--from-literal=ISSUER=https://accounts.google.com \--from-literal=CLIENT_ID=1234567890.apps.googleusercontent.com \--from-literal=SECRET=your-client-secret \-n traefik
Here's a complete example of using the middleware with Docker Compose:
version: "3.7"services:traefik:image: traefik:v3.2.1command:- "--experimental.plugins.traefikoidc.modulename=github.com/lukaszraczylo/traefikoidc"- "--experimental.plugins.traefikoidc.version=v0.7.10"volumes:- /var/run/docker.sock:/var/run/docker.sock- ./traefik-config/traefik.yml:/etc/traefik/traefik.yml- ./traefik-config/dynamic-configuration.yml:/etc/traefik/dynamic-configuration.ymllabels:- "traefik.http.routers.dash.rule=Host(`dash.localhost`)"- "traefik.http.routers.dash.service=api@internal"ports:- "80:80"hello:image: containous/whoamilabels:- traefik.enable=true- traefik.http.routers.hello.entrypoints=http- traefik.http.routers.hello.rule=Host(`hello.localhost`)- traefik.http.services.hello.loadbalancer.server.port=80- traefik.http.routers.hello.middlewares=my-plugin@filewhoami:image: jwilder/whoamilabels:- traefik.enable=true- traefik.http.routers.whoami.entrypoints=http- traefik.http.routers.whoami.rule=Host(`whoami.localhost`)- traefik.http.services.whoami.loadbalancer.server.port=8000- traefik.http.routers.whoami.middlewares=my-plugin@file
traefik-config/traefik.yml:
log:level: INFOexperimental:localPlugins:traefikoidc:moduleName: github.com/lukaszraczylo/traefikoidc# API and dashboard configurationapi:dashboard: trueinsecure: trueentryPoints:http:address: ":80"forwardedHeaders:insecure: trueproviders:docker:endpoint: "unix:///var/run/docker.sock"exposedByDefault: falsefile:filename: /etc/traefik/dynamic-configuration.yml
traefik-config/dynamic-configuration.yml:
http:middlewares:my-plugin:plugin:traefikoidc:providerURL: https://accounts.google.comclientID: 1234567890.apps.googleusercontent.comclientSecret: your-client-secretcallbackURL: /oauth2/callbacklogoutURL: /oauth2/logoutpostLogoutRedirectURI: /logged-out-pagesessionEncryptionKey: potato-secret-is-at-least-32-bytes-longscopes:- roles # Appended to defaults: ["openid", "profile", "email", "roles"]allowedUserDomains:- company.comallowedUsers:- special-user@gmail.com- contractor@external.orgallowedRolesAndGroups:- admin- developerforceHTTPS: falselogLevel: debugrateLimit: 100excludedURLs:- /login- /public- /health- /metricsheaders:# Using YAML literal style to prevent Traefik from pre-evaluating templates- 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}}
The middleware supports setting custom HTTP headers with values templated from OIDC claims and tokens. This allows you to pass authentication information to downstream services in a flexible, customized format.
Templates can access the following variables:
{{.Claims.field}} - Access individual claims from the ID token (e.g., {{.Claims.email}}, {{.Claims.sub}}){{.AccessToken}} - The raw access token string{{.IdToken}} - The raw ID token string (same as AccessToken in most configurations){{.RefreshToken}} - The raw refresh token string⚠️ Important: Template Escaping
If you encounter the error can't evaluate field AccessToken in type bool when starting Traefik, this indicates that Traefik is attempting to evaluate the template expressions before passing them to the plugin. This is a known issue when using template syntax in Traefik plugin configurations.
Solution: You must escape the template expressions using double curly braces:
headers:- name: "Authorization"value: "Bearer {{{{.AccessToken}}}}"
This is the only reliable method that works consistently. Here's why:
Double curly braces ({{{{.AccessToken}}}}) ✅
{{{{ → {{ and }}}} → }}Bearer {{.AccessToken}} reaches the Go template engine correctlyOther methods (YAML literal style, single quotes) do NOT work ❌
Working example configuration:
headers:- name: "X-User-Email"value: "{{{{.Claims.email}}}}"- name: "X-User-ID"value: "{{{{.Claims.sub}}}}"- name: "Authorization"value: "Bearer {{{{.AccessToken}}}}"- name: "X-User-Name"value: "{{{{.Claims.given_name}}}} {{{{.Claims.family_name}}}}"
Advanced template examples:
Conditional logic:
headers:- name: "X-Is-Admin"value: "{{{{if eq .Claims.role \"admin\"}}}}true{{{{else}}}}false{{{{end}}}}"
Array handling:
headers:- name: "X-User-Roles"value: "{{{{range $i, $e := .Claims.roles}}}}{{{{if $i}}}},{{{{end}}}}{{{{$e}}}}{{{{end}}}}"
Notes:
.Claims, not .claims)<no value> in the header value{{{{ and }}}}) to escape template expressions in YAML configuration filesWhen a user is authenticated, the middleware sets the following headers for downstream services:
X-Forwarded-User: The user's email address (always set)X-User-Groups: Comma-separated list of user groups (if available)X-User-Roles: Comma-separated list of user roles (if available)X-Auth-Request-Redirect: The original request URIX-Auth-Request-User: The user's email addressX-Auth-Request-Token: The user's ID token (can be large)If your downstream services return "431 Request Header Fields Too Large" errors, you can enable minimal headers mode to reduce header overhead:
http:middlewares:my-auth:plugin:traefikoidc:minimalHeaders: true# ... other config
When minimalHeaders: true is set:
X-Forwarded-UserX-Auth-Request-Token (the full ID token - often the largest header), X-Auth-Request-User, X-Auth-Request-RedirectX-User-Groups and X-User-Roles (if configured)This is particularly useful when:
See GitHub Issue #64 for details.
The middleware also sets the following security headers:
X-Frame-Options: DENYX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-originImportant: ID Token Validation
This Traefik OIDC plugin performs authentication and extracts user claims (like email, roles, groups) exclusively from the ID Token provided by your OIDC provider. It does not primarily use the Access Token for these critical functions. Therefore, it is crucial to ensure that all necessary claims are included in the ID Token itself. A common issue is that some OIDC providers might, by default, place certain claims only in the Access Token or UserInfo endpoint.
This section provides guidance on configuring popular OIDC providers to work optimally with this plugin.
Google's OIDC implementation is well-supported with automatic configuration.
access_type=offline and prompt=consent for refresh tokensoffline_access scopeemail, sub, name, given_name, family_name, picturehd claim contains the organization domainproviderURL: https://accounts.google.comAzure AD provides comprehensive enterprise OIDC support.
https://login.microsoftonline.com/{tenant-id}/v2.0email, name, preferred_username, oid by defaultAuth0 provides flexible OIDC with custom claims support.
// Auth0 Action exampleexports.onExecutePostLogin = async (event, api) => {const namespace = 'https://your-app.com/';if (event.authorization) {api.idToken.setCustomClaim(namespace + 'roles', event.authorization.roles);api.idToken.setCustomClaim('email', event.user.email);}};
postLogoutRedirectURI is in "Allowed Logout URLs"offline_access scopeOkta provides enterprise-grade OIDC with extensive customization.
/oauth2/default) or custom authorization servergroups scope for group informationKeycloak is highly configurable, requiring proper client mapper setup.
email with "Add to ID token" enabledallowedRolesAndGroups configurationhttps://your-keycloak/auth/realms/your-realmAWS Cognito provides managed OIDC with regional deployment.
GitLab supports OIDC for both GitLab.com and self-hosted instances.
openid, profile, email for basic claimsproviderURLhttps://gitlab.com as providerURLread_api scope for GitLab API access via access tokenImportant: GitHub uses OAuth 2.0, not OpenID Connect.
user:email, read:user for basic profile accessgithub.com in issuer URLAuth0 is generally OIDC compliant and works well.
// Auth0 Action to add email and roles to ID Tokenexports.onExecutePostLogin = async (event, api) => {const namespace = 'https://your-app.com/'; // Or your custom namespaceif (event.authorization) {api.idToken.setCustomClaim(namespace + 'roles', event.authorization.roles);api.idToken.setCustomClaim('email', event.user.email); // Standard claim, ensure it's there// Add other claims as needed}};
https://your-app.com/roles) are then used in the plugin's allowedRolesAndGroups or headers configuration.providerURL will be https://your-auth0-domain.auth0.com.postLogoutRedirectURI is registered in your Auth0 application settings under "Allowed Logout URLs".For other OIDC providers (e.g., Okta, Zitadel, self-hosted solutions):
.well-known/openid-configuration) at the providerURL. The plugin uses this to find authorization, token, JWKS, and end_session endpoints.openid in your scopes. profile and email are generally recommended. Add other scopes as required by your provider to release specific claims to the ID Token.For common issues and general troubleshooting, please refer to the Troubleshooting section.
Set the logLevel to debug to get more detailed logs:
logLevel: debug
providerURL is correct and accessible.sessionEncryptionKey is at least 32 bytes long.allowedUserDomains list.allowedRolesAndGroups.value: "Bearer {{{{.AccessToken}}}}"Google sessions expire after ~1 hour: If using Google as the OIDC provider and sessions expire prematurely:
offline_access scope. Google rejects this scope as invalid.access_type=offline and prompt=consent).Keycloak: Claims Missing from ID Token:
Azure AD: Group overage issues:
Auth0: Custom claims not appearing:
https://your-app.com/claimOkta: Authorization server issues:
/oauth2/default or custom)AWS Cognito: Regional endpoint errors:
cognito-idp.{region}.amazonaws.comGitLab: Self-hosted instance issues:
GitHub: Limited functionality warnings:
Environment variable names containing "API" cause plugin failure (Issue #98):
${OIDC_ENCRYPTION_SECRET_API} in Traefik configuration, the plugin fails with "invalid handler type: <nil>" errorTRAEFIK_API_* for its internal API configuration, and the "API" substring in user-defined variable names may interfere with Traefik's environment variable processing${OIDC_ENCRYPTION_SECRET_SVC} instead of ${OIDC_ENCRYPTION_SECRET_API}${OIDC_ENCRYPTION_SECRET_SERVICE}${OIDC_ENCRYPTION_SECRET_BACKEND}The middleware includes built-in warnings for provider-specific limitations. Check your logs for important notices about:
For detailed provider-specific guidance, see the Provider-Specific Configuration Examples section.
The middleware is designed with the following principles:
Contributions are welcome! Please feel free to submit a Pull Request.
-race flag to detect race conditions