From 50f9aff43aa22cadf7d30e6622ed3b317f2c6cfc Mon Sep 17 00:00:00 2001 From: Goyban Date: Tue, 1 Sep 2026 10:02:28 +0200 Subject: [PATCH] Document Authentik forwards auth for apps without SSO support, like Firefly iii --- docs/authentik-sso.md | 98 ++++++++++++++++++++++++++++++++++++++++++- firefly/README.md | 15 +++++-- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/docs/authentik-sso.md b/docs/authentik-sso.md index a074650..cf87cf7 100644 --- a/docs/authentik-sso.md +++ b/docs/authentik-sso.md @@ -24,9 +24,103 @@ app asks for: - Client secret → `.env`, never committed - Redirect URI -## Forward auth — the shape +## Forward auth — putting an app with no SSO behind Authentik -TODO: the Caddy snippet, and which services you apply it to. +This is the fallback, and it's what most self-hosted apps need. The app itself +learns nothing and changes nothing: **Caddy asks Authentik whether to allow the +request before it ever reaches the container.** An app with no accounts at all, +or one with its own login you can't replace, ends up behind a real SSO login +page either way. + +### In Authentik + +1. **Providers → Create → Proxy Provider**, mode **Forward auth (single + application)**. The external host is the public URL — `https://firefly.domain.com`. +2. **Applications → Create**, and bind it to that provider. +3. **Outposts** — add the application to an outpost. The built-in *embedded + outpost* is the one already listening on port **9000**, which is what the + Caddyfile below points at. + +Step 3 is the one that gets forgotten. Provider and application exist, the +Caddyfile is right, and every request still fails because the outpost was never +told the application is its responsibility. + +### In Caddy + +```caddyfile +firefly.domain.com { + # directive execution order is only as stated if enclosed with route. + route { + # always forward outpost path to actual outpost + reverse_proxy /outpost.goauthentik.io/* http://127.0.0.1:9000 + + # forward authentication to outpost + forward_auth http://127.0.0.1:9000 { + uri /outpost.goauthentik.io/auth/caddy + + # capitalization of the headers is important, otherwise they will be empty + copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version + + # optional, in this config trust all private ranges, should probably be set to the outposts IP + trusted_proxies private_ranges + } + + # actual site configuration below, for example + reverse_proxy 10.10.0.2:8564 + } +} +``` + +Only the last line changes per service: swap the address and port, and the +hostname at the top. Everything between is identical for every app. + +### Why each piece is there + +**`route { }` is not optional.** Caddy normally executes directives in its own +fixed order, not the order you wrote them. Without `route`, the final +`reverse_proxy` can run before `forward_auth`, and the app is served to +unauthenticated visitors. The config *looks* correct and protects nothing — +which is the worst possible failure mode for this feature, so wrap it and check +in a private window. + +**The `/outpost.goauthentik.io/*` line comes first** because the login flow +itself lives on that path. If it fell through to the app, the browser would be +sent to a URL the app doesn't serve and the redirect loop would never resolve. + +**`copy_headers` capitalization is load-bearing** — get it wrong and the headers +arrive empty rather than missing, so an app that reads them sees a blank +username instead of an error. + +**`127.0.0.1:9000` assumes Caddy and Authentik are on the same host.** If Caddy +runs on a VPS and Authentik on the NAS, this is the line to change — and +`trusted_proxies private_ranges` should then name the outpost's actual address +rather than trusting every private range. + +### What you give up + +- **The app doesn't know who you are.** It sees an authenticated-by-someone + request. Apps with their own accounts still show their own login, so you get + **two logins** — Authentik, then the app. Some apps can read the + `X-Authentik-*` headers and skip theirs; most can't. +- **Non-browser clients break.** Mobile apps, API clients and anything doing + `curl` can't complete an interactive login flow. This is why Jellyfin and + Audiobookshelf use native OIDC instead — see + [jellyfin](../jellyfin/README.md) and + [audiobookshelf](../audiobookshelf/README.md). If a service has an API you + actually call, forward auth in front of it will stop that too. +- **It is all-or-nothing per hostname.** Every path behind that block requires + login, including webhooks and health checks. + +### Where I use it + +- **[Firefly III](../firefly/README.md)** — the config above is verbatim what + runs in front of it. Firefly keeps its own login underneath, so it's two + logins, and for bank data that's a trade worth making. +- **[copyparty](../copyparty/README.md)** and other apps with their own account + system, where the second login is the price of not exposing the app's login + form to the internet. + +Prefer native OIDC wherever the app supports it. Reach for this when it doesn't. ## Groups and roles diff --git a/firefly/README.md b/firefly/README.md index 0b3a04e..0f05cab 100644 --- a/firefly/README.md +++ b/firefly/README.md @@ -221,9 +221,18 @@ behind a proxy. - [Reverse proxy with Caddy](../docs/reverse-proxy.md) — the default choice. - [Cloudflare Tunnel](../cloudflare-tunnel/) — fine for this one. It's a text app; nothing here bumps the ~100 MB body cap unless you attach large files. -- [Single sign-on with Authentik](../docs/authentik-sso.md) — **think first.** - This is your bank data, and Firefly's own login is the only thing in front of - it today. Keep a working local admin until any SSO flow is proven. +- [Single sign-on with Authentik](../docs/authentik-sso.md#forward-auth--putting-an-app-with-no-sso-behind-authentik) + — **what I do.** Firefly has no OIDC support, so it sits behind Authentik + forward auth: Caddy checks with Authentik before the request reaches the + container. That means two logins, Authentik then Firefly, which for bank data + is a trade worth making — the app's own login form is never exposed to the + internet. The exact Caddyfile is in that doc. + + Two things to keep in mind here specifically. Firefly's **API is behind the + same wall**, so external API clients stop working (the bundled importer is + unaffected — it talks to `app:8080` inside the compose network, never through + the proxy). And keep a working local admin until the flow is proven; locking + yourself out of your own ledger is an unpleasant afternoon. ## Links