From cd1e3f836da4cd1c15f201e29025c9e3d9051d4a Mon Sep 17 00:00:00 2001 From: Goyban Date: Tue, 1 Sep 2026 10:11:17 +0200 Subject: [PATCH] docs: fill in the Authentik SSO guide Forward auth with the Caddyfile used in front of Firefly III, the native OIDC endpoint reference, group mapping, and the gotchas collected while setting up jellyfin and audiobookshelf. Co-Authored-By: Claude Opus 5 --- docs/authentik-sso.md | 109 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 14 deletions(-) diff --git a/docs/authentik-sso.md b/docs/authentik-sso.md index cf87cf7..5dd8417 100644 --- a/docs/authentik-sso.md +++ b/docs/authentik-sso.md @@ -1,6 +1,8 @@ # Single sign-on with Authentik -TODO: fill in as you go. Outline below so the shape is decided in advance. +One login for everything self-hosted here, and one place to revoke it. The +services themselves stay unaware — nothing in any `compose.yaml` in this repo +mentions Authentik. ## Two ways to protect a service @@ -16,13 +18,51 @@ fallback. ## Native OIDC — the shape -TODO: provider + application setup in Authentik, then the four values every -app asks for: +Use this wherever the app supports it. You get real accounts, real logout, and +clients that aren't browsers keep working. -- Issuer / discovery URL -- Client ID -- Client secret → `.env`, never committed -- Redirect URI +### In Authentik + +**Applications → Create**, provider type **OAuth2/OpenID Connect**. Three things +to note as you go: + +- the application **slug** — it appears in half the URLs below +- the **client ID** and **client secret** +- pick a **signing key**, or the app can't validate the token it gets back + +Then add the **redirect URIs**, which is where this usually goes wrong — see the +gotchas. + +### The four values every app asks for + +| Value | Where it comes from | +|-------|--------------------| +| Issuer / discovery URL | `https://authentik.example.com/application/o//` | +| Client ID | Authentik, on the provider | +| Client secret | Authentik → the service's `.env`, never committed | +| Redirect URI | The app tells you — don't guess | + +Most apps take the issuer URL and discover the rest themselves; Audiobookshelf +calls this **Auto-populate**. When one wants the endpoints spelled out +individually: + +| Endpoint | Path | +|----------|------| +| OpenID configuration | `/application/o//.well-known/openid-configuration` | +| Authorization | `/application/o/authorize/` | +| Token | `/application/o/token/` | +| User info | `/application/o/userinfo/` | +| End session | `/application/o//end-session/` | +| JWKS | `/application/o//jwks/` | + +Note which paths carry the slug and which don't: authorize, token and userinfo +are shared across every application, while discovery, JWKS and logout are +per-application. Putting the slug where it doesn't belong gives a 404 that looks +like the provider is broken. + +Worked examples in this repo: +[audiobookshelf](../audiobookshelf/README.md#single-sign-on-with-authentik) and +[jellyfin](../jellyfin/README.md#single-sign-on-with-authentik). ## Forward auth — putting an app with no SSO behind Authentik @@ -124,16 +164,57 @@ Prefer native OIDC wherever the app supports it. Reach for this when it doesn't. ## Groups and roles -TODO: how you map Authentik groups to per-app roles, and the admin/user -split. +The mechanism is the same everywhere: Authentik puts your group membership in +the token, and the app decides what that means. + +**You usually don't need a custom mapping.** The standard `profile` scope +already includes group membership, so most apps see your groups with nothing +configured. Reach for a custom **scope mapping** (Customization → Property +Mappings) only when an app wants the claim under a different name or in a +different shape. + +**Each app consumes it differently**, and that's the part to check per service — +Audiobookshelf has group and permission claim fields under its OpenID settings; +the Jellyfin SSO-Auth plugin has its own role and admin filters. There is no +single setting that makes "the admins group" mean administrator everywhere. + +**The split worth setting up:** one group for ordinary access, one for admin, +and grant the app's admin role only to the second. The reason is the failure +mode below — if everyone who can log in is an admin, an SSO misconfiguration +isn't just an outage, it's an escalation. + +TODO: the actual group names and per-app role mapping used here, once the same +shape has survived a few services. ## Gotchas -TODO: collect these as you hit them. Known ones worth writing down: - -- Locking yourself out of an app whose only admin is now behind SSO — keep a - local fallback admin until the flow is proven. -- Redirect URI mismatches, which usually surface as a generic error. +- **Keep a local fallback admin until the flow is proven.** If the only account + that can administer an app is behind SSO and SSO breaks, you have locked + yourself out of the thing you need in order to fix it. Prove the login works, + in a private window, *before* removing the local account. +- **Redirect URI mismatches surface as a generic error** that never names the + URI. Read the exact URIs off the application's own settings page rather than + copying them from a guide — Audiobookshelf prints them, and + [its README](../audiobookshelf/README.md#the-redirect-uris-are-where-this-goes-wrong) + documents a case where the published guide was wrong for that install. +- **Behind TLS, apps build `http://` redirect URLs.** The proxy terminates TLS, + the app sees a plain HTTP request, and the callback it generates is insecure — + so the login fails halfway through. Every app has its own name for the fix; + Jellyfin's SSO-Auth plugin calls it + [Scheme Override](../jellyfin/README.md#the-setting-that-cost-me-the-time). + Firefly's `TRUSTED_PROXIES=**` is the same class of setting. +- **`email_verified` is `False` by default.** Some apps refuse to authenticate a + user whose email isn't marked verified, and the resulting error rarely says + so. Fix it in the scope mapping. +- **Forward auth breaks everything that isn't a browser.** Mobile apps and API + clients can't complete an interactive login. Check what talks to a service + before putting it behind forward auth — this is the single biggest reason to + prefer native OIDC. +- **Adding the application to an outpost is a separate step** from creating the + provider and the application, and forgetting it fails in a way that looks like + a Caddy problem. +- **The client secret belongs in the service's `.env`**, which is git-ignored. + Never in `compose.yaml`, which is committed. ## Related