kroskinski.com (~$)
jerry@homelab:~/docs

jerry@homelab:~/docs$ cat single-sign-on.md

Single Sign-On with Authelia

Authelia is the single sign-on layer sitting in front of almost everything on the homelab -- one login at auth.kroskinski.com, and the session covers every gated *.kroskinski.com subdomain. It runs two genuinely different integration modes depending on what the target app supports, and picking the right one turned out to matter more than it first looked like it would.

Two Modes, Not One

Forward-auth is for apps with no native login of their own: Caddy sends every request to Authelia first, and nothing reaches the app until Authelia confirms a valid session. Real OIDC is for apps that already speak OAuth/OIDC natively -- they get their own registered client and show a "Sign in with Authelia" button instead of being gated in front of.

The rule that settled it: if an app has real OIDC support, use it and drop forward-auth entirely. Stacking a session gate in front of an app that's already doing its own OIDC login against the same identity provider is redundant friction, not real defense in depth -- same IdP, same 2FA requirement, just asked twice.

Gotcha: a headless client fails quietly, not loudly. A webhook, a WebSocket handshake, or a cron script hitting a gated domain has no browser session to redirect -- and the failure mode rarely looks auth-related. One case surfaced as websocket: bad handshake, another as a script silently reporting success because it only checked for a 4xx/5xx exit code and never noticed the 302 redirect to a login page in between. The fix in both cases was the same: a narrowly-scoped bypass rule for that one machine-to-machine endpoint, ordered above the general policy, since Authelia evaluates rules top-to-bottom and stops at the first match.

Why It Runs on the Main Server, Not the Spare Pi

Forward-auth puts Authelia in the critical path of every single request to every gated app, continuously -- not just occasionally consulted. Running it on a separate, less reliable box would turn it into a new single point of failure independent of the main server's own health, add cross-host latency to every request, and put more continuous session writes on hardware that doesn't handle that well. It's a single lightweight Go binary; resource cost was never the deciding factor. It runs alongside Caddy instead, on the fastest possible path between the two.

Session Behaviour, and a Real Tradeoff

Session storage is the default in-memory provider, not Redis -- a deliberate simplification at household scale. The real consequence: any Authelia container restart wipes every active session, forcing a fresh login regardless of how much time was left on it. Worth remembering before restarting it mid-task.

Gotcha: one bad indent took down every gated app at once. A single misaligned line in a new OIDC client entry silently broke the config file's YAML parsing partway through -- which cascaded into Authelia reporting entirely unrelated sections as missing, confusing at first glance since those sections did exist, just past where the parser had already given up. Every gated app went down simultaneously. The rule since: run the config validator before every restart, never edit-and-restart blind.

jerry@homelab:~/docs$

cd ..