ab 80b14bee5a
Build and Publish / Build and Publish Docker Image (push) Successful in 4m22s
Update README.md
2026-05-05 15:41:39 +00:00
2026-05-05 13:10:16 +01:00
2026-05-05 13:10:16 +01:00
2026-05-05 13:10:16 +01:00
2026-05-05 13:10:16 +01:00
2026-05-05 13:10:16 +01:00
2026-05-05 15:41:10 +00:00
2026-05-05 15:41:39 +00:00

rsauth2-proxy

Auth proxy for Traefik ForwardAuth with Keycloak OIDC. Single instance protects all services in a Kubernetes cluster. Replaces oauth2-proxy.

How it works

Browser → Traefik → ForwardAuth (/auth) → rsauth2-proxy
                                              ├── no session       → 302 to Keycloak login
                                              ├── valid session    → 200 + user headers
                                              └── expired session  → token refresh → 302 back

Traefik calls /auth for every request to a protected service. The proxy checks the encrypted session cookie, verifies group membership against the route config, and returns 200 (allow), 403 (deny), or 302 (login required).

Sessions are stored entirely in an AES-256-GCM encrypted cookie. No server-side state. Any number of replicas work without coordination.

Configuration

All configuration is via environment variables.

Variable Required Default Description
AUTH_PROXY_OIDC_ISSUER yes OIDC issuer URL
AUTH_PROXY_CLIENT_ID yes Keycloak client ID
AUTH_PROXY_CLIENT_SECRET yes Keycloak client secret
AUTH_PROXY_COOKIE_SECRET yes AES-256 key, 32 bytes, base64-encoded
AUTH_PROXY_COOKIE_DOMAIN yes Cookie domain (e.g. .example.com)
AUTH_PROXY_CALLBACK_URL yes Full callback URL (e.g. https://auth.example.com/callback)
AUTH_PROXY_LISTEN no 0.0.0.0:8080 Listen address
AUTH_PROXY_ROUTES_FILE no /config/routes.yaml Path to routes config
AUTH_PROXY_LOG_LEVEL no info Log level (debug, info, warn, error)

Generate a cookie secret:

openssl rand -base64 32

Routes file

Defines which hosts are protected and which groups have access.

routes:
  grafana.example.com:
    allowed_groups: ["admins", "developers"]

  wiki.example.com:
    allowed_groups: []
    # Empty list = any authenticated user

  secret.example.com:
    allowed_groups: ["admins"]

Rules:

  • Host in routes, allowed_groups empty — any authenticated user is allowed
  • Host in routes, allowed_groups set — user must be in at least one listed group
  • Host not in routes — denied (403)

The file is polled every 5 seconds and reloaded on change. This works reliably with Kubernetes ConfigMap volume mounts.

Endpoints

Path Purpose
GET /auth ForwardAuth endpoint (called by Traefik)
GET /callback OIDC callback (receives authorization code from Keycloak)
GET /refresh Token refresh (transparent redirect when session expires)
GET /sign_out Logout (clears cookie, redirects to Keycloak end_session)
GET /health Health check (returns 200)

Keycloak setup

The proxy reads user groups from the groups claim in the ID token. Keycloak does not include this by default. Add a group membership mapper to the client:

resource "keycloak_openid_group_membership_protocol_mapper" "groups" {
  realm_id   = keycloak_realm.main.id
  client_id  = keycloak_openid_client.auth_proxy.id
  name       = "groups"
  claim_name = "groups"
  full_path  = false
}

Or manually: Client Scopes → your client → Mappers → Add mapper → "Group Membership", claim name groups, full path off.

Kubernetes deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-proxy
  namespace: auth-proxy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: auth-proxy
  template:
    metadata:
      labels:
        app: auth-proxy
    spec:
      containers:
        - name: auth-proxy
          image: ultradesu/rsauth2-proxy:0.1.0
          ports:
            - containerPort: 8080
          envFrom:
            - secretRef:
                name: auth-proxy-creds
          volumeMounts:
            - name: routes
              mountPath: /config
              readOnly: true
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
      volumes:
        - name: routes
          configMap:
            name: auth-proxy-routes
---
apiVersion: v1
kind: Service
metadata:
  name: auth-proxy
  namespace: auth-proxy
spec:
  selector:
    app: auth-proxy
  ports:
    - port: 80
      targetPort: 8080

Traefik ForwardAuth middleware

Create in each namespace that has protected services:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: auth-proxy
spec:
  forwardAuth:
    address: http://auth-proxy.auth-proxy.svc:80/auth
    trustForwardHeader: true
    authResponseHeaders:
      - X-Auth-Request-User
      - X-Auth-Request-Email
      - X-Auth-Request-Groups

Ingress for auth-proxy itself

The /callback, /refresh, and /sign_out endpoints must be reachable by browsers:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: auth-proxy
  namespace: auth-proxy
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`auth.example.com`) && (Path(`/callback`) || Path(`/refresh`) || Path(`/sign_out`))
      kind: Rule
      services:
        - name: auth-proxy
          port: 80
  tls:
    secretName: auth-proxy-tls

Building

cargo build --release

Docker

docker build -t rsauth2-proxy .

Security properties

  • Encrypted cookies — AES-256-GCM, not just signed. Cookie contents cannot be read or tampered with without the key.
  • PKCE (S256) — protects the authorization code exchange against interception.
  • Stateless PKCE — the PKCE verifier is encrypted inside the state parameter. No server-side storage needed.
  • No open redirect — the redirect URL after login is encrypted in state, not taken from user input.
  • Deny by default — any host not listed in routes gets 403.
  • JWT validation — ID tokens are verified against Keycloak's JWKS (keys refreshed hourly).
  • Cookie flagsHttpOnly, Secure, SameSite=Lax.

Response headers

On successful authentication, the following headers are set on the request forwarded to the upstream service:

Header Value
X-Auth-Request-User preferred_username from the ID token
X-Auth-Request-Email email from the ID token
X-Auth-Request-Groups Comma-separated list of groups

License

WTFPL

S
Description
No description provided
Readme WTFPL 81 KiB
Languages
Rust 99.4%
Dockerfile 0.6%