Skip to content

Latest commit

 

History

History
463 lines (345 loc) · 18.5 KB

File metadata and controls

463 lines (345 loc) · 18.5 KB

SSO Setup Guide (Authentik)

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.localhost and 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)

Prerequisites

  1. Stack is running (docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d)
  2. AUTHENTIK_BOOTSTRAP_PASSWORD is set in .env (used for first admin login)
  3. Authentik is accessible at https://auth.<DOMAIN> (prod) or http://auth.localhost (dev)

API Access (Alternative to UI)

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/

1. Initial Admin Setup

  1. Navigate to https://auth.<DOMAIN>/if/flow/initial-setup/ (or http://auth.localhost/if/flow/initial-setup/)
  2. Log in with:
    • Username: akadmin
    • Password: value of AUTHENTIK_BOOTSTRAP_PASSWORD from .env
  3. Change the admin password when prompted
  4. 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>"}'

2. Verify the Embedded Outpost

The embedded outpost handles forward auth for Traefik-proxied services. Authentik creates one automatically on first boot.

  1. Go to Applications > Outposts
  2. Verify authentik Embedded Outpost exists with type Proxy
  3. If not, create one:
    • Name: Embedded Outpost
    • Type: Proxy
    • Integration: Local (Embedded)

You'll assign applications to the outpost after creating the providers below.


3. Create Forward Auth Provider & Application for n8n

3a. Create Provider

  1. Go to Applications > Providers
  2. Click Create
  3. Select Proxy Provider
  4. 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> (or http://automation.localhost for dev)
  5. Click Finish

3b. Create Application

  1. Go to Applications > Applications
  2. Click Create
  3. Configure:
    • Name: n8n Automation
    • Slug: n8n
    • Provider: select n8n Forward Auth (the proxy provider, not any OAuth2 provider)
    • Launch URL: https://automation.<DOMAIN> (or http://automation.localhost)
  4. 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.

3c. Assign to Outpost

  1. Go to Applications > Outposts
  2. Edit the embedded outpost
  3. Add n8n Automation to the providers list
  4. Click Update

4. Create Forward Auth Provider & Application for Traefik Dashboard

4a. Create Provider

  1. Go to Applications > Providers
  2. Click Create
  3. Select Proxy Provider
  4. 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> (or http://traefik.localhost for dev)
  5. Click Finish

4b. Create Application

  1. Go to Applications > Applications
  2. Click Create
  3. Configure:
    • Name: Traefik Dashboard
    • Slug: traefik-dashboard
    • Provider: select Traefik Dashboard Forward Auth
    • Launch URL: https://traefik.<DOMAIN> (or http://traefik.localhost)
  4. Click Create

4c. Assign to Outpost

  1. Go to Applications > Outposts
  2. Edit the embedded outpost
  3. Add Traefik Dashboard to the providers list
  4. Click Update

4b. Create Forward Auth Provider & Application for Paperless-ngx

Paperless uses the same forward auth pattern as n8n. See Paperless Setup Guide for detailed steps.

Summary:

  1. Create a Proxy Provider named Paperless with forward auth (single application) mode, external URL https://docs.<DOMAIN> (or http://docs.localhost for dev)
  2. Create an Application named Paperless with slug paperless, linked to the proxy provider
  3. Add the application to the Embedded Outpost

No code changes needed — the sso-web-chain@file Traefik middleware handles authentication.


5. Create OAuth2 Provider & Application for Grafana

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 groups scope mapping for Grafana role attribution.

5a. Create Custom Groups Scope Mapping

Grafana needs a groups claim in the ID token for role mapping. Authentik doesn't include one by default.

  1. Go to Customization > Property Mappings
  2. Click Create > Scope Mapping
  3. 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()]}
  4. Click Create

5b. Create Provider

  1. Go to Applications > Providers
  2. Click Create
  3. Select OAuth2/OpenID Provider
  4. 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 (or http://monitor.localhost/login/generic_oauth for 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)
  5. Click Finish
  6. Copy the Client ID and Client Secret — you need these for .env

Critical: The signing key and the groups scope mapping are both required. Without the signing key, Grafana cannot verify the ID token. Without the groups mapping, role_attribute_path has no data to evaluate and all users get the default Viewer role.

5c. Update .env

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_oauth

Then restart Grafana: docker compose restart grafana

5d. Create Application

  1. Go to Applications > Applications
  2. Click Create
  3. Configure:
    • Name: Grafana Monitoring
    • Slug: grafana
    • Provider: select Grafana OAuth
    • Launch URL: https://monitor.<DOMAIN> (or http://monitor.localhost)
  4. Click Create

6. Create User Groups

Groups map to Grafana roles via GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH:

  • admins -> Grafana Admin
  • editors -> Grafana Editor
  • All others -> Grafana Viewer

Create groups:

  1. Go to Directory > Groups
  2. 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)
  3. Add the akadmin user to the admins group

7. Create User Accounts

  1. Go to Directory > Users
  2. Click Create for each team member
  3. Configure:
    • Username, Name, Email
    • Assign to appropriate groups (admins, editors, viewers)
  4. Set a temporary password or send an enrollment link
  5. Assign users to applications via group bindings if needed

8. Enable MFA Policies (Recommended)

  1. Go to Flows & Stages > Stages
  2. Find the default-authentication-mfa-validation stage
  3. Ensure it's included in the default authentication flow
  4. Alternatively, create a custom policy:
    • Go to Flows & Stages > Policies
    • Create an MFA Requirement Policy
    • Bind it to the default authentication flow

9. Testing

Forward Auth (n8n, Traefik dashboard)

  1. Open http://automation.localhost (dev) or https://automation.<DOMAIN> (prod) in an incognito window
  2. You should be redirected to the Authentik login page showing "Login to continue to n8n Automation"
  3. Log in with a test user account
  4. After authentication, you should be forwarded to n8n's own login page
  5. Navigate to http://traefik.localhost/dashboard/ — it should load directly (session carries over)

OAuth (Grafana)

  1. Open http://monitor.localhost/login (dev) or https://monitor.<DOMAIN>/login (prod) in an incognito window
  2. You should see a "Sign in with Authentik" button
  3. Click it — if you already have an Authentik session, it auto-authorizes
  4. 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)

Verify with curl

# 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 status

Troubleshooting

Forward auth returns 404 (services unreachable)

Traefik 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.

Forward auth returns 404 but middleware is loaded

The outpost doesn't recognize the domain. Verify:

  1. The application is assigned to the embedded outpost (check Outposts > Edit > Providers)
  2. The external host URL in the proxy provider matches the actual URL exactly
  3. Test the outpost directly from the Traefik container:
    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
    Should return 302 Found, not 404.

Traefik dashboard returns 404 in dev

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"

Grafana OAuth: "ERR_CONNECTION_RESET" on redirect

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_oauth

Note: 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.

Grafana users get Viewer role instead of Admin

  1. Verify the custom groups scope mapping exists and returns group names
  2. Verify the Grafana OAuth provider has the Grafana Groups Scope mapping assigned
  3. Verify GF_AUTH_GENERIC_OAUTH_SCOPES includes groups (not just openid profile email)
  4. Verify the user is a member of the admins group in Authentik
  5. Check by decoding the ID token or viewing the /application/o/userinfo/ response

"403 Forbidden" on forward auth services

  • 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

Redirect loops

  • Ensure Authentik itself (auth.<DOMAIN>) is NOT behind the forward auth middleware
  • Check that Traefik can reach authentik-server:9000 on the Docker network
  • Verify the outpost is using the correct authentication/authorization flows

n8n application linked to wrong provider type

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>}'

10. Create OAuth2 Provider & Application for Console

Automated: The Console Setup Wizard handles this entire section automatically. On first boot, visit console.localhost and 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.

10a. Create Provider

  1. Go to Applications > Providers
  2. Click Create
  3. Select OAuth2/OpenID Provider
  4. 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 (or http://console.localhost/api/auth/callback/authentik for 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'
  5. Click Finish
  6. 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.

10b. Update .env

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

10c. Create Application

  1. Go to Applications > Applications
  2. Click Create
  3. Configure:
    • Name: Console
    • Slug: console
    • Provider: select Console
    • Launch URL: https://console.<DOMAIN> (or http://console.localhost)
  4. Click Create

Important: The application slug must be console — this must match the AUTH_AUTHENTIK_ISSUER URL which ends in /application/o/console/.

10d. Create clients Group (Optional)

Create a clients group in Authentik for console users. This keeps client accounts separate from internal admins/editors groups.

  1. Go to Directory > Groups
  2. Click Create with name clients
  3. Add console user accounts to this group

10e. Dev Gotcha: Token/Userinfo URLs

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).

10f. Fresh Deployment Notes

On a fresh deployment with new Docker volumes:

  1. Database schema is auto-applied on container startup via entrypoint.sh (drizzle-kit push)
  2. First user login auto-creates the organization (using identity from setup wizard, or defaults) and assigns the user as admin
  3. AUTHENTIK_BOOTSTRAP_PASSWORD must be set in .env before Authentik's first boot (or restart Authentik after adding it)
  4. docker compose restart does NOT re-read .env — use docker compose up -d to recreate containers with updated env vars