This guide walks through the Authentik configuration needed to enable SSO across the stack. All steps can be done via the Authentik Admin UI or the API (/api/v3/).
Setup Wizard: Steps 5 (Grafana OAuth) and 10 (Console OAuth) are now automated by the Console Setup Wizard. On first boot, visit
console.localhostand the 3-step wizard will capture organization identity, create both OAuth2 providers, copy credentials to.env, and restart services. You only need this guide for forward auth setup (steps 3-4), user/group management (steps 6-7), or troubleshooting.
SSO Strategy:
- Forward Auth (Traefik middleware) — n8n, Paperless-ngx, Traefik dashboard
- OAuth2/OIDC (native integration) — Grafana, Console (automated by setup wizard)
- Internal only — OpenBao (no Traefik exposure, backend network only — no SSO needed)
- Stack is running (
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d) AUTHENTIK_BOOTSTRAP_PASSWORDis set in.env(used for first admin login)- Authentik is accessible at
https://auth.<DOMAIN>(prod) orhttp://auth.localhost(dev)
All steps below can be automated via the Authentik API using the bootstrap token:
# Dev: uses AUTHENTIK_BOOTSTRAP_TOKEN from docker-compose.dev.yml
curl -H "Authorization: Bearer dev-bootstrap-token" http://auth.localhost/api/v3/core/applications/
# Prod: uses AUTHENTIK_BOOTSTRAP_TOKEN from .env (generated by generate-secrets.sh)
curl -H "Authorization: Bearer $AUTHENTIK_BOOTSTRAP_TOKEN" https://auth.<DOMAIN>/api/v3/core/applications/Key API endpoints:
- Proxy providers:
/api/v3/providers/proxy/ - OAuth2 providers:
/api/v3/providers/oauth2/ - Applications:
/api/v3/core/applications/ - Outposts:
/api/v3/outposts/instances/ - Groups:
/api/v3/core/groups/ - Users:
/api/v3/core/users/ - Scope mappings:
/api/v3/propertymappings/provider/scope/
- Navigate to
https://auth.<DOMAIN>/if/flow/initial-setup/(orhttp://auth.localhost/if/flow/initial-setup/) - Log in with:
- Username:
akadmin - Password: value of
AUTHENTIK_BOOTSTRAP_PASSWORDfrom.env
- Username:
- Change the admin password when prompted
- Set up MFA on the admin account (recommended)
Tip: You can also set the password via API:
curl -X POST http://auth.localhost/api/v3/core/users/<user-pk>/set_password/ \ -H "Authorization: Bearer dev-bootstrap-token" \ -H "Content-Type: application/json" \ -d '{"password":"<new-password>"}'
The embedded outpost handles forward auth for Traefik-proxied services. Authentik creates one automatically on first boot.
- Go to Applications > Outposts
- Verify authentik Embedded Outpost exists with type
Proxy - If not, create one:
- Name:
Embedded Outpost - Type:
Proxy - Integration:
Local (Embedded)
- Name:
You'll assign applications to the outpost after creating the providers below.
- Go to Applications > Providers
- Click Create
- Select Proxy Provider
- Configure:
- Name:
n8n Forward Auth - Authorization flow:
default-provider-authorization-implicit-consent - Invalidation flow:
default-provider-invalidation-flow - Forward auth mode:
Forward auth (single application) - External host:
https://automation.<DOMAIN>(orhttp://automation.localhostfor dev)
- Name:
- Click Finish
- Go to Applications > Applications
- Click Create
- Configure:
- Name:
n8n Automation - Slug:
n8n - Provider: select
n8n Forward Auth(the proxy provider, not any OAuth2 provider) - Launch URL:
https://automation.<DOMAIN>(orhttp://automation.localhost)
- Name:
- Click Create
Important: The application must be linked to the Proxy Provider, not an OAuth2 provider. If you accidentally create an OAuth2 provider for n8n, delete it and use the proxy provider instead.
- Go to Applications > Outposts
- Edit the embedded outpost
- Add
n8n Automationto the providers list - Click Update
- Go to Applications > Providers
- Click Create
- Select Proxy Provider
- Configure:
- Name:
Traefik Dashboard Forward Auth - Authorization flow:
default-provider-authorization-implicit-consent - Invalidation flow:
default-provider-invalidation-flow - Forward auth mode:
Forward auth (single application) - External host:
https://traefik.<DOMAIN>(orhttp://traefik.localhostfor dev)
- Name:
- Click Finish
- Go to Applications > Applications
- Click Create
- Configure:
- Name:
Traefik Dashboard - Slug:
traefik-dashboard - Provider: select
Traefik Dashboard Forward Auth - Launch URL:
https://traefik.<DOMAIN>(orhttp://traefik.localhost)
- Name:
- Click Create
- Go to Applications > Outposts
- Edit the embedded outpost
- Add
Traefik Dashboardto the providers list - Click Update
Paperless uses the same forward auth pattern as n8n. See Paperless Setup Guide for detailed steps.
Summary:
- Create a Proxy Provider named
Paperlesswith forward auth (single application) mode, external URLhttps://docs.<DOMAIN>(orhttp://docs.localhostfor dev) - Create an Application named
Paperlesswith slugpaperless, linked to the proxy provider - Add the application to the Embedded Outpost
No code changes needed — the sso-web-chain@file Traefik middleware handles authentication.
Note: The Console Setup Wizard (step 3) auto-creates the Grafana OAuth2 provider with standard scopes. If you've already run the wizard, skip to step 5a only if you need the custom
groupsscope mapping for Grafana role attribution.
Grafana needs a groups claim in the ID token for role mapping. Authentik doesn't include one by default.
- Go to Customization > Property Mappings
- Click Create > Scope Mapping
- Configure:
- Name:
Grafana Groups Scope - Scope name:
groups - Description:
Map user groups for Grafana role mapping - Expression:
return {"groups": [group.name for group in request.user.ak_groups.all()]}
- Name:
- Click Create
- Go to Applications > Providers
- Click Create
- Select OAuth2/OpenID Provider
- Configure:
- Name:
Grafana OAuth - Authorization flow:
default-provider-authorization-implicit-consent - Invalidation flow:
default-provider-invalidation-flow - Client type:
Confidential - Redirect URIs:
https://monitor.<DOMAIN>/login/generic_oauth(orhttp://monitor.localhost/login/generic_oauthfor dev) - Signing key: select
authentik Self-signed Certificate(or create a new key) - Scopes/Property Mappings: select all four:
authentik default OAuth Mapping: OpenID 'openid'authentik default OAuth Mapping: OpenID 'profile'authentik default OAuth Mapping: OpenID 'email'Grafana Groups Scope(the custom mapping from step 5a)
- Name:
- Click Finish
- Copy the Client ID and Client Secret — you need these for
.env
Critical: The signing key and the
groupsscope mapping are both required. Without the signing key, Grafana cannot verify the ID token. Without the groups mapping,role_attribute_pathhas no data to evaluate and all users get the default Viewer role.
GRAFANA_OAUTH_CLIENT_ID=<client-id-from-step-above>
GRAFANA_OAUTH_CLIENT_SECRET=<client-secret-from-step-above>
GRAFANA_OAUTH_REDIRECT_URL=https://monitor.<DOMAIN>/login/generic_oauthThen restart Grafana: docker compose restart grafana
- Go to Applications > Applications
- Click Create
- Configure:
- Name:
Grafana Monitoring - Slug:
grafana - Provider: select
Grafana OAuth - Launch URL:
https://monitor.<DOMAIN>(orhttp://monitor.localhost)
- Name:
- Click Create
Groups map to Grafana roles via GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH:
admins-> Grafana Admineditors-> Grafana Editor- All others -> Grafana Viewer
- Go to Directory > Groups
- Click Create for each:
- admins — full access to all services and Grafana admin
- editors — Grafana editor role
- viewers — read-only Grafana access (optional, used as default fallback)
- Add the
akadminuser to theadminsgroup
- Go to Directory > Users
- Click Create for each team member
- Configure:
- Username, Name, Email
- Assign to appropriate groups (admins, editors, viewers)
- Set a temporary password or send an enrollment link
- Assign users to applications via group bindings if needed
- Go to Flows & Stages > Stages
- Find the
default-authentication-mfa-validationstage - Ensure it's included in the default authentication flow
- Alternatively, create a custom policy:
- Go to Flows & Stages > Policies
- Create an MFA Requirement Policy
- Bind it to the default authentication flow
- Open
http://automation.localhost(dev) orhttps://automation.<DOMAIN>(prod) in an incognito window - You should be redirected to the Authentik login page showing "Login to continue to n8n Automation"
- Log in with a test user account
- After authentication, you should be forwarded to n8n's own login page
- Navigate to
http://traefik.localhost/dashboard/— it should load directly (session carries over)
- Open
http://monitor.localhost/login(dev) orhttps://monitor.<DOMAIN>/login(prod) in an incognito window - You should see a "Sign in with Authentik" button
- Click it — if you already have an Authentik session, it auto-authorizes
- Check user profile at
/profile— it should show:- Name:
(Synced via Generic OAuth) - Email from Authentik
- Role matching the user's group membership (Admin/Editor/Viewer)
- Name:
# Forward auth: should return 302 redirect to auth.localhost
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://automation.localhost/
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://traefik.localhost/dashboard/
# Grafana: should show OAuth login option
curl -s http://monitor.localhost/login | grep -o "Sign in with Authentik"
# OpenBao: internal only (no public route)
# Verify via: docker compose exec openbao bao statusTraefik file provider failed to load. Check Traefik logs:
docker logs op1-traefik 2>&1 | grep -i "error.*file\|middleware.*does not exist"Common cause: the dynamic config YAML (config/traefik/dynamic/middleware.yml) has empty top-level keys like services: {} or routers: with only comments. Traefik v3 rejects these — remove any unused top-level keys.
The outpost doesn't recognize the domain. Verify:
- The application is assigned to the embedded outpost (check Outposts > Edit > Providers)
- The external host URL in the proxy provider matches the actual URL exactly
- Test the outpost directly from the Traefik container:
Should return
docker exec op1-traefik wget -q -S -O /dev/null \ --header="X-Forwarded-Host: automation.localhost" \ --header="X-Forwarded-Proto: http" \ --header="X-Forwarded-Uri: /" \ http://authentik-server:9000/outpost.goauthentik.io/auth/traefik 2>&1
302 Found, not404.
The base compose sets tls.certresolver=letsencrypt on the dashboard router. In dev (HTTP-only), a router with TLS config won't match the web entrypoint. The dev override must include:
- "traefik.http.routers.traefik-dashboard.tls=false"The OAuth URLs in the base compose use https://auth.${DOMAIN}/.... In dev, this becomes https://auth.localhost which doesn't exist. The dev compose override must set:
- GF_AUTH_GENERIC_OAUTH_AUTH_URL=http://auth.localhost/application/o/authorize/
- GF_AUTH_GENERIC_OAUTH_TOKEN_URL=http://authentik-server:9000/application/o/token/
- GF_AUTH_GENERIC_OAUTH_API_URL=http://authentik-server:9000/application/o/userinfo/
- GF_AUTH_GENERIC_OAUTH_REDIRECT_URL=http://monitor.localhost/login/generic_oauthNote: TOKEN_URL and API_URL use the Docker service name (authentik-server:9000) because these are server-to-server calls. auth.localhost resolves to 127.0.0.1 inside the Grafana container, which is the container itself — not Authentik.
- Verify the custom
groupsscope mapping exists and returns group names - Verify the Grafana OAuth provider has the
Grafana Groups Scopemapping assigned - Verify
GF_AUTH_GENERIC_OAUTH_SCOPESincludesgroups(not justopenid profile email) - Verify the user is a member of the
adminsgroup in Authentik - Check by decoding the ID token or viewing the
/application/o/userinfo/response
- Verify the embedded outpost is running:
docker compose logs authentik-server - Ensure the application is assigned to the outpost
- Check that the external host URL in the provider matches the actual URL
- Ensure Authentik itself (
auth.<DOMAIN>) is NOT behind the forward auth middleware - Check that Traefik can reach
authentik-server:9000on the Docker network - Verify the outpost is using the correct authentication/authorization flows
If the n8n application was linked to an OAuth2 provider instead of the Proxy Provider, forward auth won't work. Fix via API:
# Find the correct proxy provider PK
curl -s http://auth.localhost/api/v3/providers/proxy/ -H "Authorization: Bearer dev-bootstrap-token" | python3 -c "import sys,json; [print(f'pk={p[\"pk\"]} {p[\"name\"]}') for p in json.load(sys.stdin)['results']]"
# Update the application to use the proxy provider
curl -X PATCH http://auth.localhost/api/v3/core/applications/n8n/ \
-H "Authorization: Bearer dev-bootstrap-token" \
-H "Content-Type: application/json" \
-d '{"provider": <proxy-provider-pk>}'Automated: The Console Setup Wizard handles this entire section automatically. On first boot, visit
console.localhostand complete the 3-step wizard. The steps below are only needed for manual setup or troubleshooting.
The Console uses OAuth2/OIDC (same pattern as Grafana). It handles its own auth via Auth.js v5 — do not use forward auth middleware.
- Go to Applications > Providers
- Click Create
- Select OAuth2/OpenID Provider
- Configure:
- Name:
Console - Authorization flow:
default-provider-authorization-implicit-consent - Invalidation flow:
default-provider-invalidation-flow - Client type:
Confidential - Redirect URIs:
https://console.<DOMAIN>/api/auth/callback/authentik(orhttp://console.localhost/api/auth/callback/authentikfor dev) - Signing key: select
authentik Self-signed Certificate(required — Auth.js expects RS256) - Scopes/Property Mappings: select:
authentik default OAuth Mapping: OpenID 'openid'authentik default OAuth Mapping: OpenID 'profile'authentik default OAuth Mapping: OpenID 'email'
- Name:
- Click Finish
- Copy the Client ID and Client Secret
Important: The signing key is required. Without it, Authentik uses HS256 but Auth.js expects RS256, causing a "Server error" after login.
CONSOLE_OAUTH_CLIENT_ID=<client-id>
CONSOLE_OAUTH_CLIENT_SECRET=<client-secret>Then recreate the console container (restart won't re-read .env):
docker compose -f docker-compose.yml -f docker-compose.dev.yml \
-f modules/console/docker-compose.yml -f modules/console/docker-compose.dev.yml \
up -d console- Go to Applications > Applications
- Click Create
- Configure:
- Name:
Console - Slug:
console - Provider: select
Console - Launch URL:
https://console.<DOMAIN>(orhttp://console.localhost)
- Name:
- Click Create
Important: The application slug must be
console— this must match theAUTH_AUTHENTIK_ISSUERURL which ends in/application/o/console/.
Create a clients group in Authentik for console users. This keeps client accounts separate from internal admins/editors groups.
- Go to Directory > Groups
- Click Create with name
clients - Add console user accounts to this group
In dev, server-to-server calls from the console container must use Docker service names, not localhost. The dev compose override (modules/console/docker-compose.dev.yml) already sets:
- AUTH_AUTHENTIK_ISSUER=http://authentik-server:9000/application/o/console/
- AUTH_AUTHENTIK_TOKEN_URL=http://authentik-server:9000/application/o/token/
- AUTH_AUTHENTIK_USERINFO_URL=http://authentik-server:9000/application/o/userinfo/This is the same pattern as the Grafana OAuth fix (see section 5 troubleshooting).
On a fresh deployment with new Docker volumes:
- Database schema is auto-applied on container startup via
entrypoint.sh(drizzle-kit push) - First user login auto-creates the organization (using identity from setup wizard, or defaults) and assigns the user as admin
AUTHENTIK_BOOTSTRAP_PASSWORDmust be set in.envbefore Authentik's first boot (or restart Authentik after adding it)docker compose restartdoes NOT re-read.env— usedocker compose up -dto recreate containers with updated env vars