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

224 lines
8.6 KiB
Markdown

# 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.
```python
# Via MCP Tool
mcp.authentik_create_group(name="<app-slug>-users")
```
Oder via REST API:
```bash
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
```bash
# 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.
```bash
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:
```bash
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
```python
# Via MCP
mcp.authentik_add_user_to_group(group_id=<UUID>, user_id=<INT>)
```
Oder REST:
```bash
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:
```bash
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:
```bash
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)