mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
* feat: add RFC 8707 resource indicator via resource config `Config.Resource` is an alternative to `audience` for IdPs that implement RFC 8707. `buildAuthURL` sends it as the `resource` parameter on the authorization request, before `extraAuthParams` so an operator-configured `resource` key there cannot override or duplicate it. `exchangeTokens` repeats it on both the `authorization_code` and `refresh_token` grants (RFC 8707 §2.2), so a refreshed token keeps the audience binding of the original grant. Only one resource is accepted. RFC 8707 permits a list, but no provider that supports the parameter handles a list usefully: one rejects any request naming more than one resource with HTTP 400 `invalid_target`; node-oidc-provider accepts several and issues an opaque token bound to none of them. `Config.Validate()` requires an absolute URI of at most 256 characters with no fragment, wildcard, or control characters. `http`/`https` values go through `isValidSecureURL`, so HTTPS is required outside loopback; other schemes (`urn:`) pass as-is. When `resource` is set and `audience` is not, `audience` defaults to the resource for access-token validation; `strictAudienceValidation` applies the same way whichever of the two is set. That default is validation-only. A new `TraefikOidc.explicitAudience` mirrors `Config.Audience` verbatim, and `buildAuthURL` keys the non-standard `audience` request parameter (the Auth0 custom-API pattern) on it rather than on `audience`, so a resource-derived default is never sent as an unsolicited `audience` parameter alongside `resource` — a combination that misbehaves against providers such as Ory Hydra, which give `audience` a different meaning. `audience_test.go` and `url_helpers_ultra_test.go` set `audience` on the struct directly, bypassing `New()`, and now set `explicitAudience` too. docs/CONFIGURATION.md carries only the reference-table row and a short pointer; the new docs/RFC8707.md holds the request behaviour, the single-resource rationale, the `resource`/`audience` relationship, and a provider-support table from interop testing: node-oidc-provider supports it; Zitadel accepts and silently ignores it (`oidc.AuthRequest` has no `resource` field, confirmed against a live instance); Authentik returns `invalid_target`; Keycloak has no support through 26.6; Ory Hydra/Fosite uses its own `audience` parameter; Azure AD/Entra ID v2 is reported to reject `resource` combined with v2-style `scope`; Auth0 and AWS Cognito claims are unverified. README.md, docs/index.html, and .traefik.yml each point at docs/RFC8707.md instead of repeating it, and the comments on `Config.Resource`, in main.go, and in url_helpers.go defer to it as well. rfc8707_resource_test.go covers the authorization-URL parameter (set, unset, query-bearing value percent-encoded rather than merged, `extraAuthParams` unable to override), both grants sending `resource` in the form body, the `Config.Validate()` rules table, audience defaulting through `New()`, and a regression pair: a resource-derived audience default is not sent as `audience`, while an explicitly configured one still is. * fix: accept Resource in place of Audience for bearer-auth validation `NewWithContext` rejected `EnableBearerAuth=true` whenever `config.Audience` was empty, even though `t.audience` already falls back to `config.Resource` in that case. The check now fails only when both `Audience` and `Resource` are empty, and the error names both fields. `TestStartupValidation_BearerAcceptsResourceInPlaceOfAudience` covers the `Resource`-only configuration. `docs/RFC8707.md` links the `audience` parameter note to `docs/AUTH0_AUDIENCE_GUIDE.md` instead of restating it, and drops the provider table's GitHub row and the interop-testing paragraph, which are covered elsewhere.
115 lines
4.9 KiB
YAML
115 lines
4.9 KiB
YAML
displayName: Traefik OIDC
|
|
type: middleware
|
|
|
|
import: github.com/lukaszraczylo/traefikoidc
|
|
|
|
summary: |
|
|
OpenID Connect authentication middleware for Traefik. Replaces forward-auth
|
|
+ oauth2-proxy with a single plugin that auto-detects all major OIDC
|
|
providers (Google, Azure AD, Auth0, Okta, Keycloak, AWS Cognito, GitLab,
|
|
generic) and OAuth 2.0 for GitHub.
|
|
|
|
Features: ID-token validation with auto-discovery, session encryption,
|
|
proactive token refresh, RBAC via roles/groups claims, domain restriction,
|
|
templated downstream headers, security headers (CSP, HSTS, CORS), rate
|
|
limiting, PKCE, opaque-token introspection (RFC 7662), back/front-channel
|
|
logout, Dynamic Client Registration (RFC 7591), and Redis-backed shared
|
|
state for multi-replica deployments.
|
|
|
|
Full documentation: https://github.com/lukaszraczylo/traefikoidc
|
|
|
|
testData:
|
|
# Required
|
|
providerURL: https://accounts.google.com
|
|
clientID: 1234567890.apps.googleusercontent.com
|
|
clientSecret: your-client-secret
|
|
# Alternative: RFC 7523 private_key_jwt client authentication (Entra ID,
|
|
# Okta, Auth0, Keycloak). Replaces clientSecret with a signed JWT assertion.
|
|
# See README "Client authentication via private key JWT".
|
|
# clientAuthMethod: private_key_jwt
|
|
# clientAssertionKeyID: my-key-2026
|
|
# clientAssertionAlg: RS256 # default; or PS256/384/512, ES256/384/512
|
|
# # File path option:
|
|
# clientAssertionKeyPath: /etc/traefik/oidc/client-key.pem
|
|
# # Or inline PEM (PKCS#8 / PKCS#1 / SEC1):
|
|
# clientAssertionPrivateKey: |
|
|
# -----BEGIN PRIVATE KEY-----
|
|
# MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDexampleexample
|
|
# -----END PRIVATE KEY-----
|
|
sessionEncryptionKey: potato-secret-is-at-least-32-bytes-long
|
|
callbackURL: /oauth2/callback
|
|
|
|
# Common production knobs
|
|
logoutURL: /oauth2/logout
|
|
postLogoutRedirectURI: /
|
|
forceHTTPS: true # default; only set false for plaintext HTTP local dev
|
|
logLevel: info
|
|
rateLimit: 100
|
|
|
|
# Access control
|
|
allowedUserDomains:
|
|
- company.com
|
|
allowedRolesAndGroups:
|
|
- admin
|
|
- developer
|
|
excludedURLs:
|
|
- /health
|
|
- /metrics
|
|
|
|
# Scopes are appended to the defaults ["openid", "profile", "email"]
|
|
scopes:
|
|
- roles
|
|
|
|
# Templated headers forwarded to backends. Values are Go templates and must
|
|
# be written with plain double braces — the catalog analyzer feeds them to
|
|
# the plugin verbatim (issue #151; quadruple braces do NOT get collapsed).
|
|
# Only when your dynamic config file is rendered by Traefik's file-provider
|
|
# Go templating do the braces need escaping there, e.g.
|
|
# value: "{{`{{.Claims.email}}`}}"
|
|
# (see README "Templated headers").
|
|
headers:
|
|
- name: X-User-Email
|
|
value: "{{.Claims.email}}"
|
|
- name: Authorization
|
|
value: "Bearer {{.AccessToken}}"
|
|
|
|
# Security headers (default profile is enabled out of the box)
|
|
securityHeaders:
|
|
enabled: true
|
|
profile: default
|
|
|
|
# Optional: Redis for multi-replica deployments. See docs/REDIS.md.
|
|
# redis:
|
|
# enabled: true
|
|
# address: redis:6379
|
|
# password: urn:k8s:secret:redis:password
|
|
# cacheMode: hybrid
|
|
|
|
# Optional: bearer-token authentication for M2M (machine-to-machine) API
|
|
# clients. Default off. When enabled, requests presenting
|
|
# "Authorization: Bearer <jwt>" are validated against the configured OIDC
|
|
# provider (signature/issuer/audience/exp) and forwarded without creating
|
|
# a cookie session. The bearer path REJECTS ID tokens, requires a non-
|
|
# default audience, and never trusts the `email` claim as the identifier.
|
|
# See docs/BEARER_AUTH.md for the full threat model.
|
|
#
|
|
# enableBearerAuth: true # opt-in
|
|
# audience: https://api.example.com # REQUIRED when bearer is enabled
|
|
# bearerIdentifierClaim: sub # default; used as X-Forwarded-User. `email` is rejected.
|
|
# stripAuthorizationHeader: true # default; drops the raw token before forwarding
|
|
# bearerEmitWWWAuthenticate: true # default; RFC 6750 hint on 401s
|
|
# bearerOverridesCookie: false # default; cookie wins when both are present
|
|
# requireTokenIntrospection: false # opt-in; calls RFC 7662 introspection per request
|
|
# introspectionURL: https://idp.example.com/oauth/introspect # manual override; set when discovery omits introspection_endpoint
|
|
# maxTokenAgeSeconds: 86400 # 24h cap on iat (rejects clock-skew/forever tokens)
|
|
# maxIdentifierLength: 256 # cap on the sanitised principal identifier
|
|
# bearerFailureThreshold: 20 # consecutive 401s/IP that trip the throttle
|
|
# bearerFailureWindowSeconds: 60 # rolling window over which 401s are counted
|
|
# bearerFailurePenaltySeconds: 60 # 429 + Retry-After duration after threshold trips
|
|
|
|
# Optional: RFC 8707 resource indicator, alternative to `audience` for
|
|
# supporting IdPs. See docs/RFC8707.md for exact request behavior and
|
|
# provider support.
|
|
# resource: https://api.example.com
|
|
|