SSO for any app with Authentik + Traefik
What you get: put a single sign-on login screen in front of any web app — even ones with no auth of their own — using Authentik forward-auth and Traefik. Add a middleware to the router and the app is instantly SSO- protected; you can gate it to specific users or groups (including federated AD/LDAP groups). No app changes.
This is one of the most useful patterns in a homelab — and it has three gotchas that will waste an evening if you don’t know them. All three are here.
The pattern (per app)
1. Traefik — add the authentik forward-auth middleware to the router, and add an outpost route so the
/outpost.goauthentik.io/ callback reaches Authentik:
# middleware (once)
http:
middlewares:
authentik:
forwardAuth:
address: "http://AUTHENTIK_IP:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders: ["X-authentik-username","X-authentik-groups","X-authentik-email"]
# per app: the app router + its outpost route
rt-myapp:
rule: "Host(`myapp.example.com`)"
middlewares: ["lan-only","authentik"] # <- gated
service: svc-myapp
rt-myapp-outpost:
rule: "Host(`myapp.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)"
service: authentik # <- points at the Authentik outpost service
priority: 100
2. Authentik — create a Proxy Provider in forward_single mode, an Application bound to it, and gate
it to a group (Application → Policy/Group binding). Attach the provider to your embedded outpost. Done — hit
myapp.example.com and you get the Authentik login; only members of that group get through, and the app receives
X-authentik-username/-groups headers.
Bonus: federate Active Directory / LDAP into Authentik as a source, and you can gate apps by AD group membership — users log in with their existing AD password.
Gotcha 1 — login redirects to the internal IP (cert warning)
Unauthenticated requests bounce to http://AUTHENTIK_IP:9000/application/o/authorize/... instead of your public
https://auth.example.com → browser cert warning / broken flow. The embedded outpost builds that redirect from its
authentik_host, which defaults to the internal http://IP:9000.
Fix: set both authentik_host and authentik_host_browser to your public URL and restart the outpost.
# PATCH the embedded outpost — GET the config first and MERGE (a partial 'config' clobbers the rest):
# authentik_host = https://auth.example.com
# authentik_host_browser = https://auth.example.com
# then restart server + worker (a PATCH alone isn't enough — it builds the redirect at runtime):
docker compose restart server worker
Setting only authentik_host_browser is not enough — the forward-auth Location: is built from authentik_host
itself. Set both, restart, and the redirect goes to https://auth.example.com.
Gotcha 2 — expired session = “dead buttons” (POST returns 401)
After the proxy session expires, GET requests 302 to the login page (fine), but POST requests get a bare
401 — Authentik can’t redirect a request body. A single-page app then fails silently: the page loads but
every action button “does nothing.” Classic and maddening.
Fix (two parts):
- Raise the proxy provider’s
access_token_validity(default is often ~1 hour → bump tohours=12ordays=1so re-auth is ~daily, not hourly). (PATCH gotcha: includemode: forward_singleor you get400 "Internal host cannot be empty".) - App-side, reload-to-re-login on a 401 so the SPA recovers itself instead of freezing.
Gotcha 3 — a bypass PathPrefix swallows sibling routes
If you exclude an API path from auth (SDKs/CI that authenticate by token), don’t use a bare string prefix.
PathPrefix(/api/handoff) also matches /api/handoffs/... — so those app routes silently bypass the gate and
lose their X-authentik-* headers.
Fix: use a bounded regex: PathRegexp(^/api/(handoff|ingest)(/|$)) — exact endpoints with a /-or-end
boundary.
Gotchas recap
- Redirect to internal IP → set
authentik_host(+_browser) to the public URL and restart the outpost. - POST-401 dead buttons on session expiry → raise
access_token_validity+ app-side reload-on-401. - Prefix bypass over-matches → use
PathRegexpwith boundaries, notPathPrefix. - Gate by group (incl. federated AD groups), and always keep the app LAN-only unless it’s meant to be public.
Written from a working setup: Authentik (docker-compose) forward-auth gating several LAN apps via Traefik, gated to an admin group, with AD/LDAP federation for group-based access. Adjust IPs/domains to your environment.
Written by James Brooks — I run ThatNerdKnows (IT support + websites for small businesses). This is the deep end; if you’d rather just have it handled, that’s the day job.