electric-horses-infra/docs/guides/authentik-oauth2-provider.md
Benjamin Weinlich 6570e81850 docs(runbooks,guides): mirror runbooks and Authentik OIDC guide
Two Forgejo runbooks plus the Authentik OAuth2 provider guide,
mirrored from the iCloud folder into the versioned repo.

Runbooks:
- forgejo-admin-recovery.md — fallback login when Authentik is down
  using the local admin-local user (prohibit_login reset via SQL).
- forgejo-backup-restore.md — backup format, restore scenarios
  (full / DB-only / single file), disaster recovery on new host.

Guides:
- authentik-oauth2-provider.md — reusable template for adding native
  OIDC integrations in Authentik. First applied for Forgejo, ready
  to reuse for OpenProject, Nextcloud, Grafana. Includes the
  important launch-URL gotcha from ADR-0006.

Each category folder has a README.md with format conventions and
an index of the current documents.

Refs OP#1118
2026-04-11 22:26:24 +02:00

8.6 KiB

How-To: OAuth2/OIDC Provider in Authentik einrichten

Stand: 2026-04-11 Erstanwendung: M7.1 — Forgejo OIDC Setup Zielgruppe: AI-Agents und Benjamin — wiederverwendbares Template für künftige OIDC-Integrationen

Wann dieses Pattern nutzen

Wenn eine Applikation OIDC (OpenID Connect) als First-Class-Client unterstützt (z.B. Forgejo, OpenProject, Nextcloud, GitLab, Grafana, Mattermost). In diesem Fall ist nativer OIDC besser als ForwardAuth weil:

  • User-Identität (Name, Email, Avatar, Gruppen) kommt direkt in der App an
  • Auto-Registration / Account-Linking möglich
  • API-Tokens der App bleiben unabhängig
  • Migration zu anderer IdP-Software trivial

Für Apps ohne OIDC-Support (oder zu komplex zu integrieren): ForwardAuth via Embedded Outpost — siehe das LocoSoft/n8n Pattern.

Voraussetzungen

  • Authentik läuft und ist über welcome.sdda.eu erreichbar
  • SSH-Zugang authentik-sso verfügbar
  • MCP Tools authentik_* verfügbar ODER API-Token für akadmin
  • Die App (z.B. Forgejo) ist bereits live oder zumindest geplant — du brauchst die Redirect URI (meist https://<app-domain>/user/oauth2/<name>/callback oder ähnlich)

Schritt 1: Gruppe für Zugangskontrolle (optional aber empfohlen)

Statt "jeder in Authentik" → eigene Gruppe pro App, damit User-Anlage und -Abschaltung granular funktioniert.

# Via MCP Tool
mcp.authentik_create_group(name="<app-slug>-users")

Oder via REST API:

TOKEN=<akadmin-api-token>
curl -X POST 'http://localhost:9000/api/v3/core/groups/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "forgejo-users", "is_superuser": false}'

Notiere die zurückgegebene pk (UUID).

Schritt 2: OAuth2/OpenID Provider anlegen

# Vorbereitung: Flow-IDs und Scope-Mapping-IDs holen
TOKEN=<akadmin-api-token>

# Authorization Flow (Default: explicit consent)
AUTH_FLOW="9f7af462-90cf-44d8-99fa-916f8459ba13"

# Invalidation Flow (für logout)
INVAL_FLOW="697e1b4c-db65-4c21-a458-dd7df2bf51f2"

# Scope Property Mappings (diese sind bei uns fest)
SCOPE_OPENID="f878f335-46dd-4104-8dfb-fe72f21dda22"
SCOPE_PROFILE="04294271-539c-44e6-824a-5ab5c1e4613d"
SCOPE_EMAIL="602c1e0a-0c6d-48ef-bada-97d4c6e45f3c"

# Signing Certificate (RS256 statt HMAC)
CERT="663c97fa-f336-49fe-8809-118d3745547a"   # authentik Self-signed

# Eigenes Client Secret generieren
CLIENT_SECRET=$(openssl rand -hex 48)

# Provider anlegen
curl -X POST 'http://localhost:9000/api/v3/providers/oauth2/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "{
    \"name\": \"<App-Name> OIDC\",
    \"authorization_flow\": \"$AUTH_FLOW\",
    \"invalidation_flow\": \"$INVAL_FLOW\",
    \"client_type\": \"confidential\",
    \"client_secret\": \"$CLIENT_SECRET\",
    \"access_code_validity\": \"minutes=1\",
    \"access_token_validity\": \"hours=24\",
    \"refresh_token_validity\": \"days=30\",
    \"include_claims_in_id_token\": true,
    \"signing_key\": \"$CERT\",
    \"sub_mode\": \"user_email\",
    \"issuer_mode\": \"per_provider\",
    \"redirect_uris\": [
      {\"matching_mode\": \"strict\", \"url\": \"<REDIRECT_URI>\"}
    ],
    \"property_mappings\": [
      \"$SCOPE_OPENID\",
      \"$SCOPE_PROFILE\",
      \"$SCOPE_EMAIL\"
    ]
  }"

Die Response enthält:

  • pk — die ID des Providers (numerisch)
  • client_id — auto-generiert, brauchst du für die App-Config
  • client_secret — das das du mitgegeben hast (bestätigt)

Notiere client_id und client_secret sofort. Das Secret wird in der Web-UI später maskiert.

Schritt 3: Application anlegen

Die Application verbindet Provider + Namen + Zugangs-Policies.

curl -X POST 'http://localhost:9000/api/v3/core/applications/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "<App-Name>",
    "slug": "<app-slug>",
    "provider": <PROVIDER_PK>,
    "meta_launch_url": "https://<app-domain>/user/oauth2/<auth-name>",
    "meta_description": "<Beschreibung>",
    "meta_publisher": "Richter & Zech",
    "policy_engine_mode": "any"
  }'

Die Response enthält die App-ID (UUID).

Wichtig: Der slug bestimmt die Discovery-URL: https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration

KRITISCH — Silent SSO: meta_launch_url muss auf die OAuth2-Initiate-Route der App zeigen, nicht auf die App-Startseite. Nur so wird beim Klick im Authentik-Dashboard der OIDC-Flow automatisch getriggert ohne dass der User nochmal "Sign in with..." klicken muss. Beispiele:

  • Forgejo: https://code.sdda.eu/user/oauth2/authentik (der authentik Teil ist der Auth-Source-Name aus forgejo admin auth add-oauth --name)
  • OpenProject: https://openproject.sdda.eu/auth/oidc_authentik/callback
  • Grafana: https://grafana.sdda.eu/login/generic_oauth
  • Nextcloud: https://app.sdda.eu/apps/user_oidc/login/<provider_id>

Wenn man die nackte App-URL setzt (https://code.sdda.eu/), zeigt die App ihre eigene Login-Seite an, und der User muss nochmal klicken. Das ist keine silent SSO — das ist eine Frustration.

Schritt 4: Policy Binding (Gruppen-Zugangskontrolle)

Wenn du in Schritt 1 eine Gruppe angelegt hast:

APP_PK=<uuid-aus-schritt-3>
GROUP_PK=<uuid-aus-schritt-1>

curl -X POST 'http://localhost:9000/api/v3/policies/bindings/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "{
    \"target\": \"$APP_PK\",
    \"group\": \"$GROUP_PK\",
    \"enabled\": true,
    \"order\": 0,
    \"timeout\": 30
  }"

Damit können nur Mitglieder der Gruppe die App nutzen. User die nicht drin sind, bekommen beim OIDC-Login "Access Denied".

Schritt 5: User zur Gruppe hinzufügen

# Via MCP
mcp.authentik_add_user_to_group(group_id=<UUID>, user_id=<INT>)

Oder REST:

curl -X POST "http://localhost:9000/api/v3/core/groups/$GROUP_PK/add_user/" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"pk": <USER_ID>}'

Schritt 6: Discovery-URL verifizieren

Bevor du in der App den Provider konfigurierst, check dass Authentik die Discovery-URL korrekt ausliefert:

curl -s 'https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration' | jq .

Erwartete Felder:

  • issuer — muss mit dem Issuer matchen den die App erwartet
  • authorization_endpoint
  • token_endpoint
  • userinfo_endpoint
  • jwks_uri

Schritt 7: Client in der App konfigurieren

Je nach App unterschiedlich. Bei Forgejo z.B. via CLI:

docker exec -u git forgejo forgejo admin auth add-oauth \
  --name authentik \
  --provider openidConnect \
  --key "<CLIENT_ID>" \
  --secret "<CLIENT_SECRET>" \
  --auto-discover-url "https://welcome.sdda.eu/application/o/<slug>/.well-known/openid-configuration" \
  --scopes "openid profile email" \
  --skip-local-2fa

Bei OpenProject: config/settings.yml oder ENV. Bei Nextcloud: App "user_oidc" installieren, dann UI. Bei Grafana: grafana.ini [auth.generic_oauth]. Immer die App-Docs konsultieren.

Schritt 8: Test-Login

  1. Browser: App-URL → Login-Button "Sign in with authentik"
  2. Redirect zu welcome.sdda.eu → Login
  3. Evtl. Consent-Screen bestätigen
  4. Redirect zurück → User ist eingeloggt

Gotchas

  • Issuer-URL muss public sein (nicht http://10.0.0.7:9000). JWT-Signatur enthält den Issuer — wenn App und Authentik unterschiedliche Issuer sehen, schlägt die Validation fehl.
  • Subject-Mode user_email funktioniert gut wenn alle User einzigartige Email-Adressen haben. Bei gemeinsamen Mailboxen (info@ etc.) lieber hashed_user_id.
  • Include Claims in id_token = true ist wichtig für Apps die email/name aus dem id_token lesen statt separaten userinfo-Call.
  • Strict Redirect URIs: Immer "matching_mode": "strict", niemals Regex — Security.
  • Policy Timeout = 30s gibt Authentik Luft bei langsamen LDAP/SCIM-Backends.

Template-Reference

Als konkretes funktionierendes Beispiel für alle oben genannten Schritte siehe:

  • ../Forgejo/adr/0003-native-oidc-not-forwardauth.md
  • Die Forgejo-.env auf ai-apps enthält die konkreten Werte
  • Die Authentik-Admin-UI unter welcome.sdda.eu/if/admin/#/core/applications zeigt die "Forgejo" Application als lebendes Beispiel

Nächste Apps die dieses Template nutzen könnten

  • OpenProject — hat nativen OIDC-Client, aktuell noch ForwardAuth nicht genutzt
  • Nextcloud — braucht user_oidc App-Installation
  • Grafana — falls wir Monitoring einführen
  • Mattermost — falls wir Chat einführen
  • Potenzielle Migration von n8n und locosoft-hilfe-system von ForwardAuth auf OIDC (großer Eingriff, nicht dringlich)