Skip to main content

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.

PropertyValue
Imagequay.io/oauth2-proxy/oauth2-proxy:v7.7.1
Containeroauth2-proxy
URLoauth.ravenhelm.dev
Port4180 (internal)
Config~/ravenhelm/services/oauth2-proxy/
OIDC ProviderZitadel

Architecture

User Request

Traefik

ForwardAuth Check ─────────────────┐
│ │
↓ (no cookie) ↓ (valid cookie)
OAuth2-Proxy Backend Service

Zitadel OIDC

Set Cookie

Redirect to Service

Protected Services

ServiceURLBypass Paths
Homepagedashboard.ravenhelm.devNone
n8nn8n.ravenhelm.dev/webhook/*, /api/*
Homebridgehomebridge.ravenhelm.devNone

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

VariablePurpose
OAUTH2_PROXY_CLIENT_IDZitadel application client ID
OAUTH2_PROXY_CLIENT_SECRETZitadel application client secret
OAUTH2_PROXY_COOKIE_SECRET32-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:

HeaderContent
X-Auth-Request-UserUsername from ID token
X-Auth-Request-EmailEmail from userinfo
X-Auth-Request-Access-TokenJWT access token
AuthorizationBearer <access_token>

Backend services can use these headers to identify the authenticated user.


PropertyValue
Name_oauth2_proxy
Domain.ravenhelm.dev
Securetrue
HttpOnlytrue
SameSiteLax
Max-Age168 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:

  1. Check EMAIL_DOMAINS setting (use * to allow all)
  2. Verify user has email in Zitadel profile
  3. Check WHITELIST_DOMAINS includes target domain

Redirect Loop

Symptoms: Continuous redirects between OAuth2-Proxy and Zitadel

Diagnosis:

docker logs oauth2-proxy 2>&1 | grep -i "redirect\|callback"

Solutions:

  1. Verify REDIRECT_URL matches Zitadel application config
  2. Check cookie domain matches service domain
  3. Clear browser cookies for .ravenhelm.dev

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:

  1. Verify COOKIE_SECURE=true with HTTPS
  2. Check COOKIE_DOMAINS is .ravenhelm.dev (with leading dot)
  3. Verify Traefik is terminating TLS correctly

Email Claim Missing

Symptoms: X-Auth-Request-Email header empty

Solutions:

  1. Add OAUTH2_PROXY_OIDC_EMAIL_CLAIM=preferred_username
  2. Or use OAUTH2_PROXY_PROFILE_URL to fetch from userinfo endpoint
  3. Ensure Zitadel user has email set

Security Considerations

PKCE

OAuth2-Proxy uses PKCE (S256) to prevent authorization code interception:

CODE_CHALLENGE_METHOD=S256
  • HttpOnly: Prevents JavaScript access
  • Secure: Only sent over HTTPS
  • SameSite=Lax: CSRF protection

Token Validation

OAuth2-Proxy validates:

  • Token signature against JWKS
  • Token expiration
  • Token audience
  • Token issuer

References