Anmeldefluss mit PKCE (S256), State und Nonce. Das ID-Token wird gegen den JWKS des Anbieters geprueft — Signatur, Aussteller, Empfaenger, Ablauf und Nonce; alles Noetige kommt aus dem Discovery-Dokument, damit die Anbindung nicht auf Authentik festgelegt ist. Wiedererkannt wird jemand an der Kennung des Anbieters (sub), nicht an der E-Mail-Adresse — Adressen aendern sich. Ein bestehendes Konto mit derselben Adresse wird verknuepft statt verdoppelt, aber nur bei bestaetigter Adresse: sonst koennte sich beim Anbieter jemand eine fremde Adresse eintragen und damit ein hiesiges Konto uebernehmen. Uebernommen wird bewusst nur, wer jemand ist — plus die Instanz-Administration, wenn OIDC_ADMIN_GROUP gesetzt ist. Gruppen und Rollen bleiben Sache dieser Anwendung: die Gruppen im Verzeichnis bedeuten etwas anderes als die hier. Konten aus dem Verzeichnis haben kein Passwort mehr (passwordHash ist jetzt optional); Konto-Bereich und Passwort-Anmeldung sagen das statt an einer leeren Pruefung zu scheitern. Anleitung mit allen Feldern in Authentik: docs/authentik.md
252 lines
10 KiB
Markdown
252 lines
10 KiB
Markdown
# Anmeldung über Authentik (OIDC)
|
|
|
|
Diese Anleitung richtet die Anmeldung am QR-Code-Generator über **Authentik**
|
|
ein. Sie funktioniert genauso mit anderen OpenID-Connect-Anbietern — Keycloak,
|
|
Zitadel, Entra ID —, weil die Anwendung alles Nötige aus dem
|
|
Discovery-Dokument des Anbieters liest. Die Beispiele nennen Authentik, weil
|
|
die Feldnamen dort so heißen.
|
|
|
|
**Zeitbedarf:** etwa 15 Minuten.
|
|
|
|
---
|
|
|
|
## Vorher klären
|
|
|
|
| Frage | Antwort für diese Anleitung |
|
|
| --- | --- |
|
|
| Adresse der Anwendung | `https://qr.example.com` |
|
|
| Adresse von Authentik | `https://auth.example.com` |
|
|
| Läuft die Anwendung über HTTPS? | Ja — siehe Hinweis unten |
|
|
|
|
> **HTTPS ist Pflicht, sobald es über den eigenen Rechner hinausgeht.** Beim
|
|
> Anmelden gehen Token über die Leitung, und der Sitzungs-Cookie wird nur bei
|
|
> `https://` in der `BASE_URL` als `Secure` gesetzt. Für einen Versuch im LAN
|
|
> geht auch HTTP; produktiv gehört ein Reverse Proxy davor (siehe README,
|
|
> Abschnitt „Hinter einem Reverse Proxy“).
|
|
|
|
Die Anwendung muss Authentik erreichen können und umgekehrt der Browser beide.
|
|
Laufen beide in Docker auf demselben Host, genügt es meist, sie ins selbe
|
|
Netz zu hängen — der Browser braucht aber in jedem Fall öffentlich auflösbare
|
|
Adressen.
|
|
|
|
---
|
|
|
|
## Schritt 1 — In Authentik einen Provider anlegen
|
|
|
|
**Applications → Providers → Create → OAuth2/OpenID Provider**
|
|
|
|
| Feld | Wert |
|
|
| --- | --- |
|
|
| Name | `QR-Code-Generator` |
|
|
| Authorization flow | `default-provider-authorization-explicit-consent` (oder implicit, wenn niemand zustimmen soll) |
|
|
| Client type | **Confidential** |
|
|
| Client ID | notieren — kommt gleich in die `.env` |
|
|
| Client Secret | notieren — ebenfalls |
|
|
| Redirect URIs | `https://qr.example.com/api/auth/oidc/callback` |
|
|
| Signing Key | ein vorhandenes Zertifikat auswählen |
|
|
| Subject mode | `Based on the User's hashed ID` (Voreinstellung) |
|
|
|
|
Zu den **Scopes**: `openid`, `email` und `profile` müssen ausgewählt sein.
|
|
Ohne `email` liefert Authentik keine Adresse, und die Anwendung lehnt die
|
|
Anmeldung mit einer entsprechenden Meldung ab.
|
|
|
|
> **Redirect URI genau so eintragen.** Ein fehlendes `https://`, ein Schrägstrich
|
|
> zu viel oder ein anderer Hostname führt zu `redirect_uri mismatch` —
|
|
> Authentik bricht dann ab, bevor die Anwendung überhaupt etwas mitbekommt.
|
|
|
|
---
|
|
|
|
## Schritt 2 — Eine Application anlegen
|
|
|
|
**Applications → Applications → Create**
|
|
|
|
| Feld | Wert |
|
|
| --- | --- |
|
|
| Name | `QR-Code-Generator` |
|
|
| Slug | `qrcode-generator` |
|
|
| Provider | der eben angelegte Provider |
|
|
| Launch URL | `https://qr.example.com` |
|
|
|
|
Der **Slug** steckt gleich in der Issuer-Adresse — merken.
|
|
|
|
Wer den Zugang einschränken will, hängt hier unter **Policy / Group / User
|
|
Bindings** eine Gruppe ein. Das ist der empfohlene Weg: Wer sich bei Authentik
|
|
anmelden darf, kommt sonst auch in den QR-Code-Generator.
|
|
|
|
---
|
|
|
|
## Schritt 3 — Die Issuer-Adresse herausfinden
|
|
|
|
Authentik zeigt sie beim Provider unter **Provider Info** als *OpenID
|
|
Configuration Issuer*. Sie sieht so aus:
|
|
|
|
```
|
|
https://auth.example.com/application/o/qrcode-generator/
|
|
```
|
|
|
|
Prüfen lässt sich das direkt:
|
|
|
|
```bash
|
|
curl https://auth.example.com/application/o/qrcode-generator/.well-known/openid-configuration
|
|
```
|
|
|
|
Kommt ein JSON-Dokument mit `authorization_endpoint`, `token_endpoint` und
|
|
`jwks_uri` zurück, stimmt die Adresse.
|
|
|
|
---
|
|
|
|
## Schritt 4 — Die `.env` ergänzen
|
|
|
|
```bash
|
|
AUTH_ENABLED=true
|
|
|
|
OIDC_ENABLED=true
|
|
OIDC_ISSUER=https://auth.example.com/application/o/qrcode-generator/
|
|
OIDC_CLIENT_ID=<Client ID aus Schritt 1>
|
|
OIDC_CLIENT_SECRET=<Client Secret aus Schritt 1>
|
|
OIDC_BUTTON_LABEL=Mit Authentik anmelden
|
|
OIDC_SCOPES=openid email profile
|
|
OIDC_DEFAULT_ROLE=EDITOR
|
|
OIDC_ADMIN_GROUP=qr-admins
|
|
OIDC_GROUPS_CLAIM=groups
|
|
OIDC_ONLY=false
|
|
```
|
|
|
|
Danach neu starten:
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
> `OIDC_ENABLED` allein genügt nicht — **`AUTH_ENABLED` muss ebenfalls `true`
|
|
> sein**. Ohne Anmeldung gibt es keine Benutzer, denen eine Anmeldung
|
|
> zugeordnet werden könnte.
|
|
|
|
---
|
|
|
|
## Schritt 5 — Ausprobieren
|
|
|
|
`https://qr.example.com/anmelden` aufrufen. Über dem Passwortfeld steht jetzt
|
|
**„Mit Authentik anmelden“**. Ein Klick führt zu Authentik, nach der Anmeldung
|
|
geht es zurück in die Anwendung.
|
|
|
|
Alle Anmeldenden bekommen die Rolle aus `OIDC_DEFAULT_ROLE` in der
|
|
Standardgruppe. Wer die **Verwaltung** öffnen darf, hängt davon ab, ob
|
|
`OIDC_ADMIN_GROUP` gesetzt ist:
|
|
|
|
- **Leer:** Der erste Mensch, der sich anmeldet, wird Administrator dieser
|
|
Installation — genau wie bei der Ersteinrichtung mit Passwort. Danach vergibt
|
|
man das Recht unter Verwaltung → Benutzer.
|
|
- **Gesetzt:** Dann entscheidet allein die Gruppe, und zwar bei **jeder**
|
|
Anmeldung neu — auch beim allerersten Benutzer.
|
|
|
|
> **Achtung beim ersten Mal:** Wenn du `OIDC_ADMIN_GROUP` setzt, vergewissere
|
|
> dich, dass dein eigenes Konto in dieser Gruppe ist. Sonst hat niemand Zugang
|
|
> zur Verwaltung. Der Weg zurück ist einfach: die Variable leeren,
|
|
> `docker compose up -d`, neu anmelden — dann greift wieder die Regel „der
|
|
> erste wird Administrator“, sofern es noch gar keinen gibt.
|
|
|
|
---
|
|
|
|
## Was die Anwendung aus Authentik übernimmt — und was nicht
|
|
|
|
| Übernommen | Bleibt in der Anwendung |
|
|
| --- | --- |
|
|
| Wer jemand ist (`sub`) | Mitgliedschaft in Gruppen |
|
|
| E-Mail-Adresse | Rolle je Gruppe (Admin/Bearbeiter/Betrachter) |
|
|
| Anzeigename | Zuteilung von Domains |
|
|
| Instanz-Administration, falls `OIDC_ADMIN_GROUP` gesetzt ist | alles Übrige |
|
|
|
|
Das ist eine bewusste Entscheidung. Die Gruppen in Authentik und die Gruppen
|
|
hier bedeuten nicht dasselbe: dort geht es um Organisation und Zugang, hier um
|
|
die Frage, wer welche QR-Codes sehen und ändern darf. Eine automatische
|
|
Übersetzung wäre eine Quelle stiller Überraschungen — deshalb pflegt man
|
|
Gruppen und Rollen weiterhin unter **Verwaltung → Gruppen**.
|
|
|
|
Wiedererkannt wird jemand an der **Kennung von Authentik** (`sub`), nicht an
|
|
der E-Mail-Adresse. Ändert sich die Adresse, bleibt es dasselbe Konto.
|
|
|
|
### Die Admin-Gruppe
|
|
|
|
Steht in `OIDC_ADMIN_GROUP` ein Gruppenname, entscheidet Authentik bei **jeder
|
|
Anmeldung** neu, wer die Verwaltung öffnen darf: Mitglieder der Gruppe
|
|
bekommen das Recht, alle anderen verlieren es. Bleibt die Variable leer,
|
|
rührt die Anmeldung das Recht nicht an — dann pflegt man es hier.
|
|
|
|
---
|
|
|
|
## Bestehende Konten übernehmen
|
|
|
|
Gibt es hier schon Konten mit Passwort, werden sie beim ersten Anmelden über
|
|
Authentik **anhand der E-Mail-Adresse verknüpft**. Aus dem lokalen
|
|
Administrator wird also kein zweites Konto; Gruppen, Rollen und die
|
|
angelegten Codes bleiben.
|
|
|
|
Verknüpft wird nur, wenn Authentik die Adresse als **bestätigt** meldet. Sonst
|
|
könnte sich beim Anbieter jemand eine fremde Adresse eintragen und damit ein
|
|
hiesiges Konto übernehmen. Meldet Authentik sie als unbestätigt, bricht die
|
|
Anmeldung mit einer Erklärung ab.
|
|
|
|
Ist ein Konto einmal verknüpft, gibt es dafür kein Passwort mehr: Der Bereich
|
|
**Konto** zeigt dann statt der Passwortänderung einen Hinweis auf den
|
|
Anbieter.
|
|
|
|
---
|
|
|
|
## Nur noch über Authentik anmelden
|
|
|
|
```bash
|
|
OIDC_ONLY=true
|
|
```
|
|
|
|
Danach verschwindet das Passwortfeld von der Anmeldeseite.
|
|
|
|
> **Vorher an den Notausgang denken.** Fällt Authentik aus oder ist der
|
|
> Provider falsch konfiguriert, kommt niemand mehr hinein. Der Weg zurück:
|
|
> `OIDC_ONLY=false` in der `.env`, `docker compose up -d` — dann geht die
|
|
> Anmeldung mit Passwort wieder. Lege dir dafür ein lokales Administratorkonto
|
|
> an, **bevor** du umstellst.
|
|
|
|
---
|
|
|
|
## Wenn etwas nicht klappt
|
|
|
|
| Symptom | Ursache und Abhilfe |
|
|
| --- | --- |
|
|
| Der Knopf erscheint nicht | `AUTH_ENABLED` oder `OIDC_ENABLED` steht nicht auf `true`. Prüfen mit `docker compose exec app env \| grep OIDC`. Steht dort nichts, fehlt die Variable im `environment:`-Block der `docker-compose.yml` — die `.env` allein reicht nicht. |
|
|
| „Die Anmeldung über den externen Anbieter ist unvollständig eingerichtet“ | Es fehlt `OIDC_ISSUER`, `OIDC_CLIENT_ID` oder `OIDC_CLIENT_SECRET`. Die Meldung nennt, welche. |
|
|
| „Der Anmelde-Anbieter ist nicht erreichbar“ | Der Container kommt nicht an Authentik. Testen mit `docker compose exec app wget -qO- <issuer>/.well-known/openid-configuration`. Häufig: interner DNS-Name statt öffentlicher Adresse. |
|
|
| Authentik meldet `redirect_uri mismatch` | Die Redirect URI im Provider stimmt nicht exakt mit `{BASE_URL}/api/auth/oidc/callback` überein. Auf Schema, Hostname und Schrägstriche achten. |
|
|
| „Der Anbieter hat keine E-Mail-Adresse geliefert“ | Der Scope `email` fehlt im Provider, oder der Benutzer hat in Authentik keine Adresse hinterlegt. |
|
|
| „Der Anbieter hat die Anmeldung abgelehnt“ | Der Benutzer ist der Application in Authentik nicht zugeordnet — Policy/Group Binding prüfen. |
|
|
| „Der Anmeldevorgang ist abgelaufen“ | Zwischen Klick und Rückkehr sind mehr als 10 Minuten vergangen, oder der Cookie ging verloren. Häufigste echte Ursache: `BASE_URL` zeigt auf eine andere Adresse als die, über die der Browser kommt. |
|
|
| „Das Anmelde-Token des Anbieters ist ungültig“ | Uhrzeit-Abweichung zwischen den Servern oder ein gewechselter Signaturschlüssel. Beides zeigt `docker compose logs app` genauer. |
|
|
| Nach der Anmeldung fehlt die Verwaltung | `OIDC_ADMIN_GROUP` ist gesetzt, der Benutzer aber nicht in dieser Gruppe. Entweder die Gruppe in Authentik ergänzen oder die Variable leeren und das Recht hier vergeben. |
|
|
|
|
Ausführlichere Meldungen stehen im Protokoll:
|
|
|
|
```bash
|
|
docker compose logs -f app
|
|
```
|
|
|
|
---
|
|
|
|
## Was technisch passiert
|
|
|
|
1. Klick auf den Knopf → `/api/auth/oidc/start`. Die Anwendung holt das
|
|
Discovery-Dokument, würfelt `state`, `nonce` und ein PKCE-Geheimnis und legt
|
|
sie in einen kurzlebigen, `httpOnly`-Cookie.
|
|
2. Weiterleitung zu Authentik mit `code_challenge` (S256).
|
|
3. Nach der Anmeldung kommt Authentik mit `code` und `state` zurück auf
|
|
`/api/auth/oidc/callback`. Stimmt der `state` nicht, endet es hier.
|
|
4. Die Anwendung tauscht den Code gegen Token — mit Client-Geheimnis **und**
|
|
PKCE-Verifier. Ein abgefangener Code allein nützt also niemandem.
|
|
5. Das ID-Token wird gegen den JWKS von Authentik geprüft: Signatur,
|
|
Aussteller, Empfänger, Ablauf und `nonce`.
|
|
6. Konto suchen (über `sub`, ersatzweise über die bestätigte Adresse), anlegen
|
|
oder verknüpfen — und die Sitzung wie bei der Passwort-Anmeldung setzen.
|
|
|
|
Die Sitzung selbst ist danach dieselbe wie sonst: ein Zufallstoken im Cookie,
|
|
in der Datenbank nur dessen SHA-256. Abmelden beendet sie hier; bei Authentik
|
|
bleibt man angemeldet.
|