Proxy auth — Traefik ForwardAuth + OIDC
Use Traefik's forwardAuth middleware with oauth2-proxy (or Authelia/Authentik) to gate chmonitor behind an OIDC login, forwarding a full identity profile.
Traefik doesn't speak OIDC itself — it delegates the auth decision to an external service via the forwardAuth middleware, which calls that service on every request before proxying to chmonitor. Pair it with oauth2-proxy (or Authelia/Authentik) as the OIDC bridge: oauth2-proxy does the actual login against your IdP and exposes an internal check endpoint; Traefik calls it, copies the response headers it returns onto the real request, and chmonitor's trusted provider reads those into a full profile.
When to pick this path
Use this when Traefik is already your ingress (bare Traefik, Traefik in Kubernetes/k3s via IngressRoute, or Docker with the Traefik provider) and you want OIDC login without switching to nginx or Cloudflare.
Prerequisites
- Traefik v2 or v3 as your ingress (standalone, Docker provider, or Kubernetes CRD provider
IngressRoute/Middleware). - oauth2-proxy (or Authelia/Authentik) reachable from Traefik, configured against an OIDC provider (Dex, Okta, Keycloak, Google Workspace, GitHub, etc.).
- The ability to inject a static header (the shared secret) on requests forwarded to chmonitor — via a second Traefik
headersmiddleware, or by running chmonitor network-isolated behind Traefik only.
Setup
Run oauth2-proxy as the OIDC bridge
docker run -d \
--name oauth2-proxy \
-p 4180:4180 \
quay.io/oauth2-proxy/oauth2-proxy:latest \
--provider=oidc \
--oidc-issuer-url=https://dex.example.com \
--client-id=chmonitor \
--client-secret=YOUR_CLIENT_SECRET \
--cookie-secret=$(python3 -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())') \
--http-address=0.0.0.0:4180 \
--set-xauthrequest \
--scope="openid email profile groups" \
--redirect-url=https://chmonitor.example.com/oauth2/callback--scope="openid email profile groups" requests the groups claim from your OIDC provider — needed if you plan to gate chmonitor access with CHM_TRUSTED_ALLOWED_GROUPS. Your IdP (Dex, Keycloak, Authentik) must be configured to actually populate groups for this to have any effect.
Define the forwardAuth middleware
Traefik dynamic configuration (file provider) or a Kubernetes Middleware CRD:
http:
middlewares:
oauth2-proxy-auth:
forwardAuth:
address: "http://oauth2-proxy:4180/oauth2/auth"
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Preferred-Username
- X-Auth-Request-Groups
# Injects the shared secret chmonitor checks — chained after forwardAuth
chm-proxy-secret:
headers:
customRequestHeaders:
X-Chm-Proxy-Secret: "your-shared-secret-here"Kubernetes CRD equivalent:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oauth2-proxy-auth
namespace: monitoring
spec:
forwardAuth:
address: http://oauth2-proxy.monitoring.svc.cluster.local:4180/oauth2/auth
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Preferred-Username
- X-Auth-Request-Groups
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: chm-proxy-secret
namespace: monitoring
spec:
headers:
customRequestHeaders:
X-Chm-Proxy-Secret: "your-shared-secret-here"address is the exact internal check endpoint oauth2-proxy exposes; authResponseHeaders is the allowlist of headers Traefik copies from oauth2-proxy's 200 response onto the request it forwards to chmonitor. --set-xauthrequest (step 1) is what makes oauth2-proxy actually populate those headers.
Don't hardcode the secret in a committed manifest
customRequestHeaders with a literal secret value works but shouldn't be committed to source control as-is. Template it from a Kubernetes Secret at apply time (Kustomize secretGenerator, SOPS, sealed-secrets) or a CI templating step, or skip the shared secret entirely and use CHM_TRUSTED_ALLOW_INSECURE=true if chmonitor is only reachable as a ClusterIP behind Traefik (see Troubleshooting below).
Attach both middlewares to the chmonitor route
Order matters: the auth check must run before the secret header is added.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: chmonitor
namespace: monitoring
spec:
entryPoints:
- websecure
routes:
- match: Host(`chmonitor.example.com`)
kind: Rule
middlewares:
- name: oauth2-proxy-auth
- name: chm-proxy-secret
services:
- name: chmonitor
port: 8080Docker-provider label equivalent:
labels:
traefik.enable: "true"
traefik.http.routers.chmonitor.rule: "Host(`chmonitor.example.com`)"
traefik.http.routers.chmonitor.middlewares: "oauth2-proxy-auth,chm-proxy-secret"
traefik.http.middlewares.oauth2-proxy-auth.forwardauth.address: "http://oauth2-proxy:4180/oauth2/auth"
traefik.http.middlewares.oauth2-proxy-auth.forwardauth.trustForwardHeader: "true"
traefik.http.middlewares.oauth2-proxy-auth.forwardauth.authResponseHeaders: "X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Preferred-Username,X-Auth-Request-Groups"
traefik.http.middlewares.chm-proxy-secret.headers.customrequestheaders.X-Chm-Proxy-Secret: "your-shared-secret-here"You'll also need a route for oauth2-proxy's own /oauth2/ paths (login page, callback) — route PathPrefix(\/oauth2/`)` on the same host to the oauth2-proxy service directly, without the auth middlewares attached.
Configure chmonitor
Prop
Type
CHM_AUTH_PROVIDER=trusted
# oauth2-proxy's --set-xauthrequest headers are X-Auth-Request-*, not
# chmonitor's X-Forwarded-* defaults — map them explicitly
CHM_TRUSTED_USER_HEADER=X-Auth-Request-User
CHM_TRUSTED_EMAIL_HEADER=X-Auth-Request-Email
CHM_TRUSTED_NAME_HEADER=X-Auth-Request-Preferred-Username
CHM_TRUSTED_GROUPS_HEADER=X-Auth-Request-Groups
CHM_TRUSTED_AUTH_SECRET=your-shared-secret-here
# Optional: restrict to specific IdP groups
# CHM_TRUSTED_ALLOWED_GROUPS=sre,adminNetwork isolation is an alternative to the shared secret
If chmonitor runs as a Kubernetes ClusterIP Service with no other route to it besides this Traefik IngressRoute, you can skip the chm-proxy-secret middleware and set CHM_TRUSTED_ALLOW_INSECURE=true instead. Only do this when you're certain nothing else (a NodePort, a debug port-forward left open, a second Ingress) can reach chmonitor directly.
Verify
# No session — Traefik's forwardAuth call gets a 401 from oauth2-proxy,
# which should surface as a redirect to the login flow
curl -i https://chmonitor.example.com/
# Direct to the chmonitor Service, bypassing Traefik entirely — must 401
kubectl -n monitoring exec -it deploy/debug -- curl -i http://chmonitor:8080/api/v1/auth/me
# Through Traefik with a valid oauth2-proxy session cookie
curl -b "_oauth2_proxy=<cookie-value>" https://chmonitor.example.com/api/v1/auth/me | jq .Troubleshooting
Related
Proxy auth overview
Compare all three proxy patterns.
Cloudflare Access + Tunnel
Zero Trust, signed JWT, no shared secret.
nginx + oauth2-proxy
Self-hosted OIDC login in front of nginx.
Trusted forwarded headers reference
Full env var reference for the trusted provider, incl. a k3s + Traefik + oauth2-proxy + Dex example.
Authentication overview
Compare all chmonitor auth providers.