← Back to Infrastructure Documentation

Security & Authentication

Keycloak SSO and OAuth2 Proxy Architecture on linuxserver.lan

16
Protected Services
1
SSO Provider (Keycloak)
16
Keycloak Clients
1
Realm (master)
1
Admin Group

Architecture Overview

All protected services use a consistent authentication pattern: Traefik routes traffic to an OAuth2 Proxy, which validates authentication with Keycloak before forwarding to the backend service.

SSO Authentication Flow

Browser
User Request
->
Traefik
:443 (HTTPS)
->
OAuth2 Proxy
:4180
|
Keycloak
Token Validation
<-
Check Token
Valid Session?
|
Login Page
If no session
->
Authenticate
Username/Password
->
Issue Token
JWT + Session
|
Proxy Forwards
With Headers
->
Backend Service
Grafana, Portainer, etc.

Token Lifecycle

Keycloak Configuration

🔑
Keycloak Admin Console
https://keycloak.ai-servicers.com

Realm Structure

Client Configuration

Each protected service has a corresponding Keycloak client:

# Standard client settings for OAuth2 Proxy integration Client ID: {service-name} Client Type: Confidential (has client secret) Protocol: openid-connect Access Type: confidential Valid Redirect: https://{service}.ai-servicers.com/oauth2/callback Web Origins: https://{service}.ai-servicers.com

Groups & Roles

Required Keycloak Networks

Keycloak container must be accessible from:

Protected Services

All 16 services protected by OAuth2 Proxy + Keycloak SSO:

Service Auth Proxy Container URL Keycloak Client
AList (File Manager) alist-auth-proxy alist.ai-servicers.com alist
ArangoDB (Graph DB) arangodb-auth-proxy arangodb.ai-servicers.com arangodb
Dashy (Dashboard) dashy-auth-proxy dashy.ai-servicers.com dashy
Dozzle (Docker Logs) dozzle-auth-proxy dozzle.ai-servicers.com dozzle
Draw.io (Diagrams) drawio-auth-proxy drawio.ai-servicers.com drawio
Grafana (Monitoring) grafana-auth-proxy grafana.ai-servicers.com grafana
Loki (Log Aggregation) loki-auth-proxy loki.ai-servicers.com loki
Microbin (Paste Service) microbin-auth-proxy microbin.ai-servicers.com microbin
Mongo Express (MongoDB UI) mongo-express-auth-proxy mongo-express.ai-servicers.com mongo-express
Netdata (System Metrics) netdata-auth-proxy netdata.ai-servicers.com netdata
Obsidian LiveSync obsidian-auth-proxy obsidian.ai-servicers.com obsidian
OpenMemory UI (Mem0) openmemory-ui-auth-proxy openmemory.ai-servicers.com openmemory
Portainer (Docker Mgmt) portainer-auth-proxy portainer.ai-servicers.com portainer
Qdrant (Vector DB) qdrant-auth-proxy qdrant.ai-servicers.com qdrant
Redis Commander redis-commander-auth-proxy redis-commander.ai-servicers.com redis-commander
Stirling PDF stirling-pdf-auth-proxy stirling.ai-servicers.com stirling-pdf

OAuth2 Proxy Configuration

Standard configuration template for new OAuth2 Proxy instances:

# OAuth2 Proxy Container Configuration # Image: quay.io/oauth2-proxy/oauth2-proxy:latest # === OIDC Provider Settings === OAUTH2_PROXY_PROVIDER="keycloak-oidc" OAUTH2_PROXY_CLIENT_ID="{service-name}" OAUTH2_PROXY_CLIENT_SECRET="{from-keycloak}" # === OIDC Discovery (Bypass for internal routing) === OAUTH2_PROXY_SKIP_OIDC_DISCOVERY="true" OAUTH2_PROXY_OIDC_ISSUER_URL="https://keycloak.ai-servicers.com/realms/master" OAUTH2_PROXY_OIDC_JWKS_URL="http://keycloak:8080/realms/master/protocol/openid-connect/certs" OAUTH2_PROXY_LOGIN_URL="https://keycloak.ai-servicers.com/realms/master/protocol/openid-connect/auth" OAUTH2_PROXY_REDEEM_URL="http://keycloak:8080/realms/master/protocol/openid-connect/token" # === Upstream Service === OAUTH2_PROXY_UPSTREAMS="http://{service}:{port}/" OAUTH2_PROXY_HTTP_ADDRESS="0.0.0.0:4180" # === Cookie Configuration === OAUTH2_PROXY_COOKIE_SECRET="{32-byte-base64}" # Generate with: openssl rand -base64 32 OAUTH2_PROXY_COOKIE_SECURE="true" OAUTH2_PROXY_COOKIE_DOMAINS=".ai-servicers.com" # === Email/User Settings === OAUTH2_PROXY_EMAIL_DOMAINS="*" # Allow all email domains OAUTH2_PROXY_PASS_USER_HEADERS="true" OAUTH2_PROXY_SET_XAUTHREQUEST="true" # === Reverse Proxy Mode === OAUTH2_PROXY_REVERSE_PROXY="true" OAUTH2_PROXY_REDIRECT_URL="https://{service}.ai-servicers.com/oauth2/callback"

Critical Configuration Notes

OIDC Discovery Bypass
We use SKIP_OIDC_DISCOVERY=true because OAuth2 Proxy connects to Keycloak internally via http://keycloak:8080, but the issuer URL in tokens is https://keycloak.ai-servicers.com. This mismatch causes validation failures without the bypass.
💡
Cookie Secret Generation
Generate a secure cookie secret: openssl rand -base64 32 | tr -d '\n'
Store in secrets file: $HOME/projects/secrets/{service}.env

Required Networks (3-Network Pattern)

Each OAuth2 Proxy container must be on three networks:

Adding a New Protected Service

Step-by-step guide to protect a new service with OAuth2 Proxy and Keycloak SSO:

1 Create Keycloak Client
  1. Go to Keycloak Admin Console
  2. Navigate to Clients -> Create client
  3. Set Client ID: {service-name}
  4. Client type: OpenID Connect
  5. Enable Client authentication (confidential)
  6. Valid redirect URIs: https://{service}.ai-servicers.com/oauth2/callback
  7. Web origins: https://{service}.ai-servicers.com
  8. Copy the client secret from Credentials tab
2 Create Secrets File
# Create $HOME/projects/secrets/{service}.env KEYCLOAK_CLIENT_ID="{service-name}" KEYCLOAK_CLIENT_SECRET="{from-step-1}" OAUTH2_COOKIE_SECRET="$(openssl rand -base64 32 | tr -d '\n')"
3 Add OAuth2 Proxy to deploy.sh
# Load secrets set -a source $HOME/projects/secrets/{service}.env set +a # Deploy OAuth2 Proxy container docker run -d \ --name {service}-auth-proxy \ --network traefik-net \ --restart unless-stopped \ -e OAUTH2_PROXY_PROVIDER=keycloak-oidc \ -e OAUTH2_PROXY_CLIENT_ID=$KEYCLOAK_CLIENT_ID \ -e OAUTH2_PROXY_CLIENT_SECRET=$KEYCLOAK_CLIENT_SECRET \ -e OAUTH2_PROXY_COOKIE_SECRET=$OAUTH2_COOKIE_SECRET \ -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \ -e OAUTH2_PROXY_OIDC_ISSUER_URL=https://keycloak.ai-servicers.com/realms/master \ -e OAUTH2_PROXY_OIDC_JWKS_URL=http://keycloak:8080/realms/master/protocol/openid-connect/certs \ -e OAUTH2_PROXY_LOGIN_URL=https://keycloak.ai-servicers.com/realms/master/protocol/openid-connect/auth \ -e OAUTH2_PROXY_REDEEM_URL=http://keycloak:8080/realms/master/protocol/openid-connect/token \ -e OAUTH2_PROXY_UPSTREAMS=http://{service}:{port}/ \ -e OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 \ -e OAUTH2_PROXY_COOKIE_SECURE=true \ -e OAUTH2_PROXY_COOKIE_DOMAINS=.ai-servicers.com \ -e OAUTH2_PROXY_EMAIL_DOMAINS=* \ -e OAUTH2_PROXY_PASS_USER_HEADERS=true \ -e OAUTH2_PROXY_REVERSE_PROXY=true \ -e OAUTH2_PROXY_REDIRECT_URL=https://{service}.ai-servicers.com/oauth2/callback \ --label "traefik.enable=true" \ --label "traefik.http.routers.{service}.rule=Host(\`{service}.ai-servicers.com\`)" \ --label "traefik.http.routers.{service}.entrypoints=websecure" \ --label "traefik.http.routers.{service}.tls.certresolver=letsencrypt" \ --label "traefik.http.services.{service}.loadbalancer.server.port=4180" \ quay.io/oauth2-proxy/oauth2-proxy:latest # Connect to additional networks docker network connect keycloak-net {service}-auth-proxy docker network connect {service}-net {service}-auth-proxy # Or appropriate service network
4 Deploy and Verify
# Deploy ./deploy.sh # Check container status docker ps | grep {service}-auth-proxy # Check logs for errors docker logs {service}-auth-proxy --tail 20 # Test authentication flow curl -I https://{service}.ai-servicers.com # Should redirect to Keycloak login

Troubleshooting

Symptom: 401 Unauthorized / Token validation failed
Solution: Check OIDC discovery bypass configuration

OAuth2 Proxy must use internal URLs for token validation but external URL for issuer:

# Verify these settings: OAUTH2_PROXY_SKIP_OIDC_DISCOVERY="true" OAUTH2_PROXY_OIDC_JWKS_URL="http://keycloak:8080/..." # Internal OAUTH2_PROXY_REDEEM_URL="http://keycloak:8080/..." # Internal OAUTH2_PROXY_LOGIN_URL="https://keycloak.ai-servicers.com/..." # External (browser redirect)
Symptom: Redirect loop / ERR_TOO_MANY_REDIRECTS
Solution: Check cookie domain and secure settings
  • Ensure COOKIE_DOMAINS=.ai-servicers.com (note leading dot)
  • Ensure COOKIE_SECURE=true when using HTTPS
  • Clear browser cookies and try again
  • Check redirect URL matches Keycloak client configuration
Symptom: 502 Bad Gateway
Solution: Verify network connectivity
# Check OAuth2 Proxy can reach backend service docker exec {service}-auth-proxy wget -O- http://{service}:{port}/health # Verify network attachments docker inspect {service}-auth-proxy --format '{{json .NetworkSettings.Networks}}' | jq . # Connect missing networks docker network connect {service}-net {service}-auth-proxy
Symptom: "i/o timeout" connecting to Keycloak
Solution: OAuth2 Proxy not on keycloak-net
# Connect to keycloak network docker network connect keycloak-net {service}-auth-proxy # Verify connection docker exec {service}-auth-proxy ping -c 1 keycloak
Symptom: Access denied after login / Group check failed
Solution: Verify user group membership in Keycloak
  • Go to Keycloak Admin -> Users -> Select user
  • Check Groups tab for /administrators membership
  • If no group restriction needed, remove --allowed-group from OAuth2 Proxy config

Debug Commands

# View OAuth2 Proxy logs docker logs {service}-auth-proxy -f # Check Keycloak logs docker logs keycloak --tail 50 | grep -i error # Verify Keycloak client exists curl -s http://keycloak:8080/realms/master/.well-known/openid-configuration | jq . # Test token endpoint (requires valid credentials) curl -X POST http://keycloak:8080/realms/master/protocol/openid-connect/token \ -d "client_id={client}" \ -d "client_secret={secret}" \ -d "grant_type=client_credentials"

Quick Reference

URLs

Files

Networks