Files
serverAppConfigs/docs/Infrastructure/Authentik.md
T
Frank 3cc99d81cc Authentik SSO + homepage naked domain
Auth layer (Authentik 2026.8.0):
- compose: 3-service stack (authentik-postgres, authentik-server, authentik-worker),
  postgres health-gated, bind mounts under /home/dank/authentik
- Caddyfile: auth.franksdank.cloud vhost; forward_auth on the no-auth vhosts
  (homepage, SearXNG, Hermes, Prometheus, RomM, Copyparty); outpost path
  proxied first in a route block
- .env.example: AUTHENTIK_PG_PASS + AUTHENTIK_SECRET_KEY
- deploy.sh: render only CADDY_BASICAUTH_HASH (the homepage per-user hashes
  are retired; grafana + pihole keep basicauth)
- tests/authentik.bats (new) + updated homepage/caddyfile/pihole seams
- docs: Authentik.md + per-service pages; Caddy.md + Homepage.md updated

Homepage (naked domain):
- Caddyfile: dankclaw.franksdank.cloud -> franksdank.cloud www.franksdank.cloud;
  dankclaw + dankserver redirect to it; bare-IP catch-all updated
- addn-hosts: A records for the naked domain, www, and auth (all -> 143)
- tests/pihole.bats: vhost_names handles multi-address blocks; TLD check
  allows the naked domain

All 146 tests pass.
2026-08-24 23:48:27 -07:00

6.3 KiB
Raw Blame History

Authentik — the homelab's SSO / OIDC provider

Authentik is the single sign-on (SSO) and OpenID Connect (OIDC) provider for the homelab. It fronts the Caddy vhosts that do not carry their own application-level authentication. Apps that have their own login (Grafana, Gitea, OpenWebUI, Jellyfin, the *arr stack, Pi-hole) are not wrapped in forward_auth — they handle their own session.

  • Version: 2026.8.0 (pinned in dankclaw/compose.yaml)
  • Host: dankclaw (192.168.0.143)
  • Name: auth.franksdank.cloud
  • Stack: 3 compose services — authentik-postgres, authentik-server, authentik-worker (see Pi-hole for the LAN-DNS pattern)

Why Authentik

The comparison that chose it (against Authelia, Keycloak, Zitadel, Ory) is in .docaudit/research/auth.md. In short: full OIDC with single-logout, a web GUI for all configuration, a native Caddy forward_auth outpost, and a footprint that fits on a 3-user homelab (~1–1.5 GB idle). Redis was removed in 2025.10, so the stack is 3 services, not 4.

Architecture

graph LR
    subgraph Browser
        U[User]
    end
    subgraph Caddy [Caddy - auth gate]
        FA[forward_auth]
    end
    subgraph AK [Authentik]
        SRV[server :9000]
        WKR[worker]
        PG[(postgres)]
        OUT[outpost /auth/caddy]
    end
    subgraph App [Target app]
        A1[SearXNG / Hermes / Prometheus<br/>RomM / Copyparty / homepage]
    end

    U -->|1. GET /| FA
    FA -->|2. GET /outpost.../auth/caddy| OUT
    OUT -->|3. no session: 302 login| U
    U -->|4. login at auth.franksdank.cloud| SRV
    SRV --> PG
    WKR --> PG
    SRV -->|5. 2xx: session valid| FA
    FA -->|6. proxy with headers| A1
  1. The browser hits a protected vhost (e.g. search.franksdank.cloud).
  2. Caddy forward_auth clones the request to the Authentik outpost (/outpost.goauthentik.io/auth/caddy).
  3. With no valid session, the outpost returns a 302 to the Authentik login page (auth.franksdank.cloud).
  4. The user logs in at Authentik (email + password, TOTP/WebAuthn MFA, or an upstream provider).
  5. On the next request the outpost returns 2xx; Caddy copies the X-Authentik-* headers and proxies to the app.

The three compose services

Service Role Image
authentik-postgres PostgreSQL 16 (users, flows, sessions, events) docker.io/library/postgres:16-alpine
authentik-server Core + admin API + the embedded Caddy outpost (port 9000) ghcr.io/goauthentik/server:2026.8.0
authentik-worker Background tasks (event processing, provider sync, flows) ghcr.io/goauthentik/server:2026.8.0
  • Postgres is the only stateful store. Sessions, caching, and WebSockets moved to PostgreSQL in 2025.10 (Redis removed).
  • Server and worker run the same image; the command selects the role (server / worker).
  • The worker runs as 0:0 (rootless: container root maps to the host user that owns the data dir).
  • Data persists under /home/dank/authentik/ (bind mounts, repo policy — no named volumes).

Caddy integration

The Caddyfile wraps the no-auth vhosts in a route block. The pattern (one example; the others are identical):

search.franksdank.cloud {
	route {
		# 1. The outpost path is proxied to Authentik (login UI + session).
		reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
		# 2. forward_auth checks the session before serving the app.
		forward_auth authentik-server:9000 {
			uri /outpost.goauthentik.io/auth/caddy
			copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
			trusted_proxies private_ranges
		}
		# 3. The app.
		reverse_proxy 192.168.0.143:8081
	}
}

Vhosts protected by forward_auth: the homepage (franksdank.cloud, www.franksdank.cloud), SearXNG, Hermes, Prometheus, RomM, Copyparty. The auth.franksdank.cloud vhost proxies to the server directly (no forward_auth — it is the login).

Configuration (secrets)

Two secrets live in the host .env (git-ignored; placeholders in .env.example):

  • AUTHENTIK_PG_PASS — the PostgreSQL password. Generate: openssl rand -base64 32.
  • AUTHENTIK_SECRET_KEY — the server's signing key. Generate: openssl rand -base64 48.

First-time setup

  1. ./scripts/deploy.sh dankclaw — pulls, starts the 3 services (postgres health-gated), reloads Caddy.
  2. Open https://auth.franksdank.cloud — the initial setup wizard runs. It creates the superuser (email + password) and generates the DB schema.
  3. In the admin interface: Directory → Applications → add a Proxy application (or an OAuth2/OpenID Connect provider) for each vhost you protect. The Caddy outpost reads the application's policy.
  4. For apps with native OIDC (Grafana, Gitea, OpenWebUI): add an OAuth2/OpenID Connect provider, create the app with the correct redirect URI, and paste the client_id / client_secret into the app's SSO settings.

Operations

  • Health: https://auth.franksdank.cloud/-/health/ (the server healthcheck probes this).
  • Logs: podman logs authentik-server / podman logs authentik-worker.
  • Backups: pg_dump the authentik-postgres database (the single source of truth). The /home/dank/authentik/ dir holds media + static files.
  • Upgrades: bump the image tag in dankclaw/compose.yaml, run ./scripts/deploy.sh dankclaw. Authentik migrates the schema on startup.

Failure modes

Symptom Cause Fix
Every vhost 502s at the outpost authentik-server down podman logs authentik-server; check postgres health
Login loops (302 → login → 302) trusted_proxies / X-Forwarded-* mismatch Confirm Caddy sets X-Forwarded-For; the outpost trusts private_ranges
"Invalid signature" on the outpost AUTHENTIK_SECRET_KEY differs between server and worker Both services read the same .env var; re-deploy
Postgres "password authentication failed" AUTHENTIK_PG_PASS changed after first init Reset via pg_ctl or re-init the data dir

Cross-references

  • Caddy — the reverse proxy that carries the forward_auth directives
  • Pi-hole — the LAN DNS that resolves auth.franksdank.cloud
  • Network Architecture — the flat naming scheme
  • Research: .docaudit/research/auth.md — the full provider comparison