Single Sign-On with authentik
Put authentik behind Traefik at https://auth.example.com, give Grafana real OIDC single sign-on, and protect an app that has no SSO of its own with Traefik forward authentication.
Documentation: authentik Docker Compose install · Proxy provider with Traefik · authentik Grafana integration
Verification: the configuration was taken from the three pages above (read on 7 November 2026). Nothing here was run: authentik was not installed for this recipe. The internal address of the embedded outpost in step 5 is derived from the documented path and port rather than quoted, and the authentik menu names change between releases. Test each step as you go.
Goal #
One login at auth.example.com for your self-hosted apps. Grafana signs users in through OIDC and maps authentik entitlements to Grafana roles. An app without SSO (here whoami.example.com) is only reachable after authentik has authenticated the browser.
Prerequisites #
- A Docker host with Traefik and TLS as in Docker Service Deploy, and the shared
proxynetwork - DNS records for
auth.example.com,grafana.example.comandwhoami.example.compointing at the host - Grafana already running (Monitor a Homelab with Prometheus and Grafana)
- A backup routine that will cover authentik’s database and secret key (Off-Site Docker Host Backup with restic)
- A way to reach authentik without authentik: keep the
akadminrecovery path (step 8) and your console access
Never lock yourself out. Keep one admin session open while you protect apps, and keep a local login on Grafana until SSO works.
Steps #
1. Install authentik #
Follow authentik (install section) in /opt/stacks/authentik: download the official Compose file, write PG_PASS and AUTHENTIK_SECRET_KEY into .env, then:
cd /opt/stacks/authentik
docker compose pull
docker compose up -dNever change AUTHENTIK_SECRET_KEY after the first start, and back it up with the database. Pin the image tag with AUTHENTIK_TAG (Automate Container Updates Safely).
2. Put it behind Traefik #
Remove the published ports from the server service (or bind them to 127.0.0.1), join the proxy network under an unambiguous alias, and add labels. The official file names the service server, a name that is too generic for a shared network, hence the alias:
services:
server:
networks:
default: {}
proxy:
aliases: [authentik-server]
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.authentik.rule=Host(`auth.example.com`)
- traefik.http.routers.authentik.entrypoints=websecure
- traefik.http.routers.authentik.tls.certresolver=letsencrypt
- traefik.http.services.authentik.loadbalancer.server.port=9000
networks:
proxy:
external: trueMerge this into the downloaded file (for example in a compose.override.yaml) so upgrades of the official file stay simple. Then set the akadmin password at https://auth.example.com/if/flow/initial-setup/ and enable MFA for your admin account.
3. Create a user and a group #
In the admin interface create a normal user for yourself (do not use akadmin day to day) and, for Grafana, the application entitlements in step 4. Create the user under Directory, Users; menu names (unverified).
4. Grafana with OIDC #
In authentik: create an application with an OAuth2/OpenID Connect provider (Applications, Applications, Create with Provider). Follow authentik (Add an OIDC Application) and the Grafana integration page:
- Client type Confidential; note the Client ID, Client Secret and the slug (
grafana) - Redirect URI, type Strict:
https://grafana.example.com/login/generic_oauth - Logout URI
https://grafana.example.com/logout, method Front-channel - Select a Signing Key, and under Advanced protocol settings, Selected Scopes add the mapping authentik default OAuth Mapping: Application Entitlements
- On the application, create entitlements
Grafana Admins,Grafana EditorsandGrafana Viewers, and bind your user or a group to them. The names must match Grafana’srole_attribute_pathexactly
In Grafana, the environment variables (secrets from files, not the Compose file):
environment:
GF_SERVER_ROOT_URL: https://grafana.example.com
GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
GF_AUTH_GENERIC_OAUTH_NAME: authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: <CLIENT_ID>
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE: /run/secrets/grafana_oidc_secret
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email entitlements
GF_AUTH_GENERIC_OAUTH_AUTH_URL: https://auth.example.com/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: https://auth.example.com/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL: https://auth.example.com/application/o/userinfo/
GF_AUTH_SIGNOUT_REDIRECT_URL: https://auth.example.com/application/o/grafana/end-session/
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(entitlements[*], 'Grafana Admins') && 'Admin' || contains(entitlements[*], 'Grafana Editors') && 'Editor' || 'Viewer'"Add the grafana_oidc_secret file as a Compose secret, as in the monitoring recipe. The __FILE form of the client secret follows Grafana’s general __FILE convention (Grafana); it is unverified for this setting. The documented integration uses the entitlements claim; the Grafana example in the authentik sheet uses a groups claim instead, which works only if you map groups into the token yourself. Check the discovery document:
curl -s https://auth.example.com/application/o/grafana/.well-known/openid-configuration | jq -r .issuerRestart Grafana (docker compose up -d), open it in a private window and use Sign in with authentik. Leave oauth_auto_login off until this works.
5. Forward auth for an app without SSO #
In authentik: create an application whoami with a Proxy provider in Forward auth (single application) mode, external host https://whoami.example.com, and add it to the embedded outpost (Applications, Outposts). Single-application mode lets you bind different policies to each app; domain-level mode protects several apps under one parent domain but cannot enforce per-app rules.
In Traefik, the middleware and a router that sends the outpost path to authentik (from the official Traefik page). As labels on the authentik server service:
labels:
- traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik
- traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true
- traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uidThe authentik-server:9000 address is the embedded outpost on the alias from step 2 (derived, unverified). The outpost listens on port 9000 for HTTP. The same middleware is also shown as a file-provider block in authentik (Forward Auth). On the protected app:
whoami:
image: traefik/whoami:<VERSION>
networks: [proxy]
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
- traefik.http.routers.whoami.entrypoints=websecure
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
- traefik.http.routers.whoami.middlewares=authentik@docker
- traefik.http.routers.whoami-outpost.rule=Host(`whoami.example.com`) && PathPrefix(`/outpost.goauthentik.io/`)
- traefik.http.routers.whoami-outpost.entrypoints=websecure
- traefik.http.routers.whoami-outpost.tls.certresolver=letsencrypt
- traefik.http.routers.whoami-outpost.priority=15
- traefik.http.routers.whoami-outpost.service=authentik@dockerThe second router (priority 15, above the app router) sends /outpost.goauthentik.io/ on the app’s host to authentik so the login redirect and callback work. It is only needed in single-application mode. The traefik.http.services.authentik... service from step 2 is what authentik@docker refers to. Validate before starting:
docker compose config -q
docker compose up -d6. Restrict who may use each app #
Bind a group or user to each application in authentik (the application’s Policy / Group / User Bindings tab, name unverified). An application with no binding is open to every authenticated authentik user.
7. Add a second factor and recovery #
Enable MFA in the authentication flow or per user (TOTP or WebAuthn), and save the recovery codes. Create a recovery link for yourself with the command in authentik (Operations).
8. Plan for authentik being down #
Everything behind forward auth is unreachable when authentik is, so monitor it (Monitor a Homelab with Prometheus and Grafana), back up its database and AUTHENTIK_SECRET_KEY, and keep Grafana’s local admin login. Do not protect authentik, Traefik’s dashboard or your monitoring behind authentik alone.
Verify #
curl -sI https://auth.example.com/-/health/ready/ | head -n 1
curl -s https://auth.example.com/application/o/grafana/.well-known/openid-configuration | jq -r .issuer
curl -sI https://whoami.example.com | head -n 3 # redirect to authentik, not the app- Opening Grafana and choosing Sign in with authentik lands you back in Grafana with the role from your entitlement.
- Opening
https://whoami.example.comin a private window sends you to authentik and, after login, shows the app with anX-Authentik-Usernameheader. The/-/health/ready/path is unverified; any200fromauth.example.comshows Traefik reaches it. - A user without the binding gets an access-denied page.
Troubleshooting #
| SYMPTOM | FIX |
|---|---|
redirect_uri mismatch or Redirect URI Error | The URI in authentik must equal Grafana’s callback exactly, https://grafana.example.com/login/generic_oauth, and GF_SERVER_ROOT_URL must match how users reach Grafana |
| Grafana login works but everyone is a Viewer | The entitlements scope or mapping is missing, or the entitlement names differ from the role path |
| Redirect loop on the protected app | The outpost router is missing or has a lower priority than the app router; check the PathPrefix rule and priority 15 |
502 on the outpost path | Wrong address or port in the middleware, or server is not on the proxy network under the alias |
| Login loops behind the proxy | Pass the Host and X-Forwarded-* headers and allow websockets (authentik, troubleshooting) |
| Browser shows the default Traefik certificate | Router rule does not match or the challenge failed (TLS Troubleshooting) |
| Locked out of authentik | Create a recovery key from the CLI, see authentik (Operations) |
| Encrypted fields unusable after restore | AUTHENTIK_SECRET_KEY differs from the original |
Rollback / Cleanup #
- Undo protection for one app: remove the
middlewaresand outpost router labels from the app and rundocker compose up -d. The app is public again, so also firewall it. - Undo Grafana SSO: remove the
GF_AUTH_GENERIC_OAUTH_*variables, restart Grafana and log in with the local admin account. - Remove authentik:
docker compose downin/opt/stacks/authentik; add-vonly to delete the database volume as well (destructive, take a dump first, see Database Dumps).
Related Sheets #
- authentik · Keycloak · Security · CrowdSec
- Traefik · Grafana · Reverse Proxy Patterns · Nginx
- Docker-Compose · PostgreSQL Cheat-Sheet · Database Dumps · curl · jq
- Docker Service Deploy · Monitor a Homelab with Prometheus and Grafana · Off-Site Docker Host Backup with restic · Expose a Service with Cloudflare Tunnel · TLS Troubleshooting