OAuth2-Proxy
Forward authentication proxy for protecting services without native OIDC support.
Overview
OAuth2-Proxy provides SSO protection for services via Traefik's ForwardAuth middleware, handling the OIDC flow and managing session cookies.
| Property | Value |
|---|---|
| Image | quay.io/oauth2-proxy/oauth2-proxy:v7.7.1 |
| Container | oauth2-proxy |
| URL | oauth.ravenhelm.dev |
| Port | 4180 (internal) |
| Config | ~/ravenhelm/services/oauth2-proxy/ |
| OIDC Provider | Zitadel |
Architecture
User Request
↓
Traefik
↓
ForwardAuth Check ─────────────────┐
│ │
↓ (no cookie) ↓ (valid cookie)
OAuth2-Proxy Backend Service
↓
Zitadel OIDC
↓
Set Cookie
↓
Redirect to Service
Protected Services
| Service | URL | Bypass Paths |
|---|---|---|
| Homepage | dashboard.ravenhelm.dev | None |
| n8n | n8n.ravenhelm.dev | /webhook/*, /api/* |
| Homebridge | homebridge.ravenhelm.dev | None |
Configuration
docker-compose.yml
services:
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
container_name: oauth2-proxy
restart: unless-stopped
networks:
- ravenhelm_net
environment:
# OIDC Provider
- OAUTH2_PROXY_PROVIDER=oidc
- OAUTH2_PROXY_PROVIDER_DISPLAY_NAME=Zitadel
- OAUTH2_PROXY_OIDC_ISSUER_URL=https://auth.ravenhelm.dev
- OAUTH2_PROXY_CLIENT_ID=${OAUTH2_PROXY_CLIENT_ID}
- OAUTH2_PROXY_CLIENT_SECRET=${OAUTH2_PROXY_CLIENT_SECRET}
# Cookie Settings
- OAUTH2_PROXY_COOKIE_SECRET=${OAUTH2_PROXY_COOKIE_SECRET}
- OAUTH2_PROXY_COOKIE_DOMAINS=.ravenhelm.dev
- OAUTH2_PROXY_COOKIE_SECURE=true
# Access Control
- OAUTH2_PROXY_EMAIL_DOMAINS=*
- OAUTH2_PROXY_WHITELIST_DOMAINS=.ravenhelm.dev
# Proxy Settings
- OAUTH2_PROXY_REDIRECT_URL=https://oauth.ravenhelm.dev/oauth2/callback
- OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
- OAUTH2_PROXY_REVERSE_PROXY=true
# Header Passing
- OAUTH2_PROXY_SET_XAUTHREQUEST=true
- OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
- OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
# OIDC Settings
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
- OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
- OAUTH2_PROXY_SCOPE=openid profile email
- OAUTH2_PROXY_USER_ID_CLAIM=sub
- OAUTH2_PROXY_OIDC_EMAIL_CLAIM=preferred_username
# Upstream (ForwardAuth mode)
- OAUTH2_PROXY_UPSTREAMS=static://200
labels:
# Traefik routing for OAuth2-Proxy itself
- "traefik.enable=true"
- "traefik.http.routers.oauth2-proxy.rule=Host(`oauth.ravenhelm.dev`)"
- "traefik.http.routers.oauth2-proxy.entrypoints=websecure"
- "traefik.http.routers.oauth2-proxy.tls.certresolver=letsencrypt"
- "traefik.http.services.oauth2-proxy.loadbalancer.server.port=4180"
# ForwardAuth middleware definition
- "traefik.http.middlewares.oauth2-proxy-auth.forwardauth.address=http://oauth2-proxy:4180/"
- "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-Access-Token,Authorization"
Environment Variables
| Variable | Purpose |
|---|---|
OAUTH2_PROXY_CLIENT_ID | Zitadel application client ID |
OAUTH2_PROXY_CLIENT_SECRET | Zitadel application client secret |
OAUTH2_PROXY_COOKIE_SECRET | 32-byte base64 secret for cookies |
Protect a Service
Add Traefik labels to any service to protect it with OAuth2-Proxy:
services:
my-service:
labels:
- "traefik.enable=true"
- "traefik.http.routers.my-service.rule=Host(`myservice.ravenhelm.dev`)"
- "traefik.http.routers.my-service.entrypoints=websecure"
- "traefik.http.routers.my-service.tls.certresolver=letsencrypt"
- "traefik.http.routers.my-service.middlewares=oauth2-proxy-auth@docker"
- "traefik.http.services.my-service.loadbalancer.server.port=8080"
The key is: middlewares=oauth2-proxy-auth@docker
Bypass Authentication
For webhooks or APIs that need to bypass auth, use higher-priority routes:
labels:
# Main route (protected)
- "traefik.http.routers.n8n.rule=Host(`n8n.ravenhelm.dev`)"
- "traefik.http.routers.n8n.middlewares=oauth2-proxy-auth@docker"
- "traefik.http.routers.n8n.priority=1"
# Webhook route (unprotected, higher priority)
- "traefik.http.routers.n8n-webhooks.rule=Host(`n8n.ravenhelm.dev`) && PathPrefix(`/webhook`)"
- "traefik.http.routers.n8n-webhooks.priority=10"
Request Headers
After authentication, OAuth2-Proxy passes these headers to backend services:
| Header | Content |
|---|---|
X-Auth-Request-User | Username from ID token |
X-Auth-Request-Email | Email from userinfo |
X-Auth-Request-Access-Token | JWT access token |
Authorization | Bearer <access_token> |
Backend services can use these headers to identify the authenticated user.
Cookie Details
| Property | Value |
|---|---|
| Name | _oauth2_proxy |
| Domain | .ravenhelm.dev |
| Secure | true |
| HttpOnly | true |
| SameSite | Lax |
| Max-Age | 168 hours (7 days) |
Quick Commands
# View logs
docker logs -f oauth2-proxy
# Restart
docker restart oauth2-proxy
# Check health
curl -I http://oauth2-proxy:4180/ping
# Test authentication flow
curl -v https://oauth.ravenhelm.dev/oauth2/start
# Rotate cookie secret
openssl rand -base64 32
# Update OAUTH2_PROXY_COOKIE_SECRET in .env
docker restart oauth2-proxy
Troubleshooting
403 Forbidden After Login
Symptoms: Login succeeds but service returns 403
Diagnosis:
docker logs oauth2-proxy 2>&1 | grep -i "error\|403\|forbidden"
Solutions:
- Check
EMAIL_DOMAINSsetting (use*to allow all) - Verify user has email in Zitadel profile
- Check
WHITELIST_DOMAINSincludes target domain
Redirect Loop
Symptoms: Continuous redirects between OAuth2-Proxy and Zitadel
Diagnosis:
docker logs oauth2-proxy 2>&1 | grep -i "redirect\|callback"
Solutions:
- Verify
REDIRECT_URLmatches Zitadel application config - Check cookie domain matches service domain
- Clear browser cookies for
.ravenhelm.dev
Cookie Not Set
Symptoms: Auth succeeds but cookie not persisted
Diagnosis:
# Check cookie in response
curl -v https://oauth.ravenhelm.dev/oauth2/callback?... 2>&1 | grep -i "set-cookie"
Solutions:
- Verify
COOKIE_SECURE=truewith HTTPS - Check
COOKIE_DOMAINSis.ravenhelm.dev(with leading dot) - Verify Traefik is terminating TLS correctly
Email Claim Missing
Symptoms: X-Auth-Request-Email header empty
Solutions:
- Add
OAUTH2_PROXY_OIDC_EMAIL_CLAIM=preferred_username - Or use
OAUTH2_PROXY_PROFILE_URLto fetch from userinfo endpoint - Ensure Zitadel user has email set
Security Considerations
PKCE
OAuth2-Proxy uses PKCE (S256) to prevent authorization code interception:
CODE_CHALLENGE_METHOD=S256
Cookie Security
HttpOnly: Prevents JavaScript accessSecure: Only sent over HTTPSSameSite=Lax: CSRF protection
Token Validation
OAuth2-Proxy validates:
- Token signature against JWKS
- Token expiration
- Token audience
- Token issuer