- Python 71.5%
- Shell 28.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitea/workflows | ||
| blueprints | ||
| custom-templates | ||
| media/public | ||
| scripts | ||
| .env.example | ||
| .gitignore | ||
| .yamllint | ||
| docker-compose.yml | ||
| nextcloud-install-oidc.md | ||
| README.md | ||
Authentik – zentrales Login (SSO) für FKF ev
Selbst gehosteter Authentik-Server als zentrales Login-System des Vereins. Die gesamte Konfiguration (Branding, Scopes, angebundene Anwendungen) liegt als Blueprints in diesem Repository und wird automatisch angewendet. Gepinnte Version: 2026.8.4.
Architektur
Internet ──TLS──> externer Reverse Proxy (nginx) ──HTTP──> 127.0.0.1:9000 ──> server ──┐
worker ──┼──> postgresql
│
./blueprints (read-only) ──> /blueprints/custom ./media ──> /data/media │
./custom-templates ──> /templates Volume "database" <──────────────┘
| Dienst | Aufgabe |
|---|---|
server |
Weboberfläche, Login-Flows, OIDC-Endpunkte, API |
worker |
Hintergrundaufgaben: Blueprints anwenden, E-Mails versenden, Aufräumen |
postgresql |
Datenbank (einziger Datenspeicher; Redis wird in dieser Version nicht mehr benötigt) |
Der Reverse Proxy terminiert TLS. Authentik stellt nur HTTP bereit, standardmäßig an
127.0.0.1 gebunden (AUTHENTIK_BIND_ADDRESS/AUTHENTIK_PORT_HTTP in .env).
Repository-Struktur
| Pfad | Inhalt |
|---|---|
docker-compose.yml |
Stack (postgresql, server, worker) |
.env.example |
Vorlage aller Variablen; echte .env nur auf dem Server |
blueprints/00-branding.yaml |
Brand: Titel, Logo, Favicon, CSS |
blueprints/10-scope-mappings.yaml |
OIDC-Scope verein (Claim mitgliedsnummer) |
blueprints/apps/ |
Eine Datei pro angebundener Anwendung; example-app.yaml = Vorlage |
blueprints/apps/nextcloud.yaml |
Nextcloud (OIDC), siehe Abschnitt „Nextcloud“ |
media/public/ |
Logo (hell/dunkel) und Favicon |
custom-templates/ |
Eigene E-Mail-Templates (derzeit leer) |
scripts/generate-secrets.sh |
Erzeugt .env mit Zufallswerten |
scripts/check-blueprints.py |
Offline-Prüfung der Blueprints |
scripts/validate.sh |
Alle Prüfungen (lokal = CI) |
.gitea/workflows/ |
validate.yml (Push/PR), deploy.yml (nur master) |
Abweichungen von Standardanleitungen (Authentik 2026.8)
- Dateipfade für Logo/Favicon: Absolute Pfade wie
/media/public/logo.svgwerden in 2026.8 abgelehnt. Authentik speichert Dateien unter/data/media/public/und erwartet in Blueprints nur den Dateinamen (branding_logo: "fkf-logo-%(theme)s.png").%(theme)swird durchlight/darkersetzt. - Eigenes CSS: Das Brand-Feld
branding_custom_cssexistiert in 2026.8.4, der „Alternativweg“ (Custom-CSS-Datei) ist daher nicht nötig. Selektoren (ak-flow-executor::part(main),--pf-global--primary-color--*) wurden gegen den Quellcode der Version geprüft; Authentik hat aber keine stabile CSS-API – nach Updates die Login-Seite kurz ansehen. - Flow-Felder:
flow_invalidation(Brand) undinvalidation_flow(Provider) heißen in dieser Version so. - Docker-Socket/
certs: Gegenüber dem offiziellen Compose entfernt (nur für Outposts per Docker nötig). - Logo: Das Original ist schwarz auf transparent. Für den Dunkelmodus gibt es
fkf-logo-dark.pngmit weißem Schriftzug; das Favicon wurde aus dem Badge erzeugt. Bei neuem Logo beide Varianten ersetzen.
Ersteinrichtung
Voraussetzungen: Linux-Server mit Docker + Compose-Plugin, Git, ein Reverse Proxy mit TLS für auth.ts-q.de.
git clone <repo-url> /opt/authentik && cd /opt/authentik
./scripts/generate-secrets.sh # erzeugt .env (überschreibt nie eine vorhandene)
$EDITOR .env # SMTP-Zugang, Admin-E-Mail und ggf. Port/Bind-Adresse eintragen
docker compose up -d
docker compose logs -f server worker
- Reverse Proxy einrichten (siehe unten), DNS für
auth.ts-q.desetzen. - Admin-Konto: Es gibt zwei Wege.
- Vorab konfiguriert (empfohlen):
generate-secrets.shsetztAUTHENTIK_BOOTSTRAP_PASSWORDauf ein Zufallspasswort. Beim allerersten Start legt Authentik damit den Benutzerakadminan (E-Mail ausAUTHENTIK_BOOTSTRAP_EMAIL). Anmelden unterhttps://auth.ts-q.de/mitakadminund dem Passwort aus der.env. Danach Passwort in der Oberfläche ändern, MFA einrichten und dieAUTHENTIK_BOOTSTRAP_*-Zeilen aus der.envauf dem Server entfernen. Die Initial-Setup-Seite ist dann gesperrt („Zugriff verweigert“), niemand kann das System übernehmen. Die Variablen wirken nur beim ersten Anlegen des Benutzers. Existiertakadminschon, ändern spätere Werte nichts. Statt des Klartext-Passworts geht auchAUTHENTIK_BOOTSTRAP_PASSWORD_HASH(Django-Hash-Format). Optional legtAUTHENTIK_BOOTSTRAP_TOKENeinen API-Token für den Admin an. - Per Initial-Setup: Die Bootstrap-Zeilen aus der
.enventfernen, vor dem ersten Starthttps://auth.ts-q.de/if/flow/initial-setup/aufrufen und dort das Passwort fürakadminsetzen. Das sofort tun, die Seite ist bis dahin für jeden offen.
- Vorab konfiguriert (empfohlen):
- In der Admin-Oberfläche unter System → Einstellungen die Base URL auf
https://auth.ts-q.desetzen (wird ab Authentik 2026.11 Pflicht). - Prüfen, dass die Blueprints gelaufen sind (siehe „Blueprints anwenden und prüfen“).
AUTHENTIK_SECRET_KEYsichern (Passwortmanager) und nie ändern.
Das erste Hochfahren dauert 1–2 Minuten (Datenbank-Migration). Schlägt ein Blueprint beim allerersten Start fehl, weil ein mitgelieferter Standard-Flow noch nicht angelegt war, hilft
docker compose restart worker.
Externer Reverse Proxy
Authentik braucht: den originalen Host, X-Forwarded-Proto/X-Forwarded-For und WebSocket-Support
(Upgrade/Connection; wird u. a. für Outposts und Live-Updates genutzt).
Ab 2026.8 vertraut Authentik diesen Headern nur, wenn die Verbindung aus einem vertrauenswürdigen Netz kommt
(AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS; Standard: private Netze inkl. Docker-Bridges, siehe .env.example).
Liegt der Proxy in einem anderen Netz, dieses eintragen – sonst erkennt Authentik https nicht und
Weiterleitungen/Cookies verhalten sich falsch.
Beispiel nginx:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
http2 on;
server_name auth.ts-q.de;
ssl_certificate /etc/letsencrypt/live/auth.ts-q.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.ts-q.de/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9000; # AUTHENTIK_BIND_ADDRESS:AUTHENTIK_PORT_HTTP
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
server {
listen 80;
server_name auth.ts-q.de;
return 301 https://$host$request_uri;
}
Läuft der Proxy auf einem anderen Rechner: AUTHENTIK_BIND_ADDRESS auf die interne IP des Servers setzen
und den Port per Firewall nur für den Proxy öffnen.
Portainer
Läuft der Stack als Portainer-Stack (z. B. aus dem Git-Repository), gibt es zwei Besonderheiten.
1. Variablen: Die .env liegt nicht im Repo. In Portainer lädst du sie im Stack-Formular
(„Load variables from .env file“). Portainer nutzt die Werte für ${…} in der Compose-Datei und legt sie als
stack.env im Stack-Ordner ab. Die Compose-Datei liest deshalb .env und stack.env, beide sind optional.
Nach dem ersten Start prüfen, ob die Container die Werte haben:
docker exec <worker-container> printenv NEXTCLOUD_CLIENT_ID (kommt nichts zurück, fehlen sie und die
App-Blueprints schlagen fehl).
2. Bind-Mounts: Portainer checkt das Repo nach /data/compose/<id> aus. Das ist ein Pfad im
Portainer-Container. Der Docker-Daemon sucht ./blueprints, ./media und ./custom-templates aber auf dem
Host. Ohne Anpassung kommt: Bind mount failed: '/data/compose/<id>/media' does not exist.
Lösung: PROJECT_DIR (siehe .env.example) auf den Host-Pfad des Repos setzen. Zwei Wege:
- Stack-Ordner von Portainer verwenden (kein zusätzlicher Klon nötig). Läuft Portainer mit einem benannten
Volume
portainer_data, ist der Host-Pfad$(docker volume inspect portainer_data --format '{{.Mountpoint}}')/compose/<id>, meistPROJECT_DIR=/var/lib/docker/volumes/portainer_data/_data/compose/<id>. Die<id>steht in der Fehlermeldung bzw. in der URL des Stacks. Sie ändert sich, wenn der Stack neu angelegt wird. DannPROJECT_DIRnachziehen. Bei Portainer-Updates aus Git bleiben die Dateien im Ordner aktuell. (Nicht auf allen Setups getestet.) - Eigener Klon auf dem Host:
git clone <repo-url> /opt/authentik, dannPROJECT_DIR=/opt/authentik. Der Klon muss bei Änderungen pergit pullaktuell gehalten werden (z. B. durchdeploy.yml). Blueprint-Änderungen greifen erst nach demgit pull.
Die Business Edition von Portainer kennt zusätzlich die Option „Enable relative path volumes“, die dasselbe
löst. Außerhalb von Portainer (docker compose im Projektordner) bleibt PROJECT_DIR leer.
Neue Anwendung anbinden
blueprints/apps/example-app.yamlnachblueprints/apps/<app>.yamlkopieren.- In der Kopie alle mit ANPASSEN markierten Stellen ändern:
metadata.name, Provider-name,slug,redirect_uris,meta_launch_url, Namen der Env-Variablen. Das Labelblueprints.goauthentik.io/instantiate: "false"entfernen (es verhindert das Anwenden der Vorlage). - Client-Typ wählen:
- confidential – App mit Backend (z. B. Yii2): Client-Secret wird serverseitig verwendet.
- public + PKCE – reine Browser-/Mobile-Apps (z. B. React-SPA):
client_type: public,client_secretweglassen, in der App PKCE (S256) aktivieren.
- In
.envauf dem Server die Variablen ergänzen (<APP>_CLIENT_ID,<APP>_CLIENT_SECRET; Secret z. B. mitopenssl rand -hex 32). Ebenfalls als Platzhalter in.env.exampledokumentieren. Die Variablen stehen nur auf dem Server; im Repo steht nie ein Secret. ./scripts/validate.shausführen, committen, nachmasterpushen. Die.envmuss vorher auf dem Server ergänzt sein;docker compose up -derstelltserverundworkerbei geänderter Umgebung neu.- In der App eintragen (Endpunkte findet man in Authentik am Provider unter „OpenID-Konfigurations-URL“):
- Issuer/Discovery:
https://auth.ts-q.de/application/o/<slug>/.well-known/openid-configuration - Scopes:
openid profile email verein - Claim
mitgliedsnummersteht (beiinclude_claims_in_id_token: true) im ID-Token und im Userinfo.
- Issuer/Discovery:
- Die Mitgliedsnummer pflegt man am Nutzer unter Verzeichnis → Nutzer → Attribute:
mitgliedsnummer: "12345".
Nextcloud
Angebunden ist https://nextcloud.ts-q.net per OpenID Connect. Auf der Nextcloud-Seite dient dafür die App
„OpenID Connect user backend“ (user_oidc).
Was in Authentik eingerichtet ist
Alles steckt in blueprints/apps/nextcloud.yaml (Provider + Application) und blueprints/10-scope-mappings.yaml
(Scope nextcloud). Die Zugangsdaten kommen aus der .env (NEXTCLOUD_CLIENT_ID, NEXTCLOUD_CLIENT_SECRET,
von generate-secrets.sh erzeugt).
| Einstellung | Wert | Grund |
|---|---|---|
| Provider-Name | Nextcloud (OIDC) |
eindeutiger Name, darüber wird er wiedergefunden |
| Client-Typ | confidential |
Nextcloud hat ein Backend und hält das Secret geheim |
| Redirect-URI (Login) | https://nextcloud.ts-q.net/apps/user_oidc/code, strikt |
fester Rücksprungpfad der App user_oidc |
| Redirect-URI (Post Logout) | https://nextcloud.ts-q.net, strikt |
Ziel nach der Abmeldung |
| Back-Channel-Logout | https://nextcloud.ts-q.net/apps/user_oidc/backchannel-logout/authentik |
Abmeldung in Authentik beendet auch die Nextcloud-Sitzung |
| Grant-Types | authorization_code, refresh_token |
Standard-Login mit Code-Austausch |
sub |
user_uuid |
stabile, nicht erratbare Kennung (Empfehlung der Authentik-Doku) |
| Signatur | RS256 mit dem Zertifikat „authentik Self-signed Certificate“ | Tokens sind für Nextcloud prüfbar |
| Flows | default-provider-authorization-implicit-consent, default-provider-invalidation-flow |
kein Zustimmungsdialog |
| Scopes | openid, email, profile, nextcloud |
siehe unten |
| Application | Name Nextcloud, Slug nextcloud |
Slug steckt in der Discovery-URL |
Scope nextcloud (Mapping „FKF: Scope 'nextcloud'“) liefert zusätzlich:
user_id: der Benutzername, damit Nextcloud-Konten lesbar heißen (statt UUID).quota: Speicherplatz, gepflegt als Attributnextcloud_quotaan der Gruppe oder am Nutzer (z. B.5 GB,none; der Nutzerwert gewinnt). Ohne Attribut gilt Nextclouds Standard.- Gruppen kommen nicht aus diesem Scope, sondern aus dem Standard-Scope
profile(Claimgroups, direkte Gruppen des Nutzers). Würde der Nextcloud-Scope sie zusätzlich liefern, stünde jede Gruppe doppelt im Token (Authentik hängt Listen aus mehreren Mappings aneinander).
Wer sich anmelden darf: aktuell alle Authentik-Nutzer. Zur Einschränkung am Application-Eintrag in der Oberfläche eine Gruppen-Bindung („Policy / Group / User Bindings“) setzen.
Was du in Nextcloud einträgst
- App OpenID Connect user backend installieren (Apps → Sicherheit).
- Verwaltungseinstellungen → OpenID Connect → neuen Provider anlegen:
| Feld in Nextcloud | Wert |
|---|---|
| Identifier | authentik (muss zur Back-Channel-Logout-URL oben passen) |
| Client ID / Client secret | NEXTCLOUD_CLIENT_ID / NEXTCLOUD_CLIENT_SECRET aus der .env auf dem Server |
| Discovery endpoint | https://auth.ts-q.de/application/o/nextcloud/.well-known/openid-configuration |
| Scope | openid email profile nextcloud |
| User ID mapping | user_id |
| Display name mapping | name |
| Email mapping | email |
| Quota mapping | quota |
| Groups mapping | groups |
| Use group provisioning | an |
| Use unique user ID | aus, damit Konten user_id (Benutzername) heißen und bestehende Nextcloud-Konten mit gleichem Namen übernommen werden |
- Optional: Anmeldeseite von Nextcloud überspringen und direkt zu Authentik leiten:
sudo -u www-data php occ config:app:set --type=string --value=0 user_oidc allow_multiple_user_backends(Danach geht lokaler Nextcloud-Login nur noch überhttps://nextcloud.ts-q.net/login?direct=1.)
Hinweise:
- Die Redirect-URIs sind strikt. Ändert sich die Nextcloud-Adresse, müssen
redirect_urisundlogout_uriinblueprints/apps/nextcloud.yamlsowiemeta_launch_urlangepasst werden. - Nextcloud muss
auth.ts-q.deper HTTPS erreichen (Discovery, Token- und Userinfo-Abruf laufen serverseitig). - Ein Nextcloud-Admin ergibt sich über die Nextcloud-Gruppe
admin; eine gleichnamige Authentik-Gruppe wird per Gruppen-Provisioning dorthin übernommen (nicht getestet).
Was geprüft wurde (lokale Authentik-Testinstanz, ohne echte Nextcloud)
- Blueprint wird fehlerfrei angewendet; Discovery liefert RS256,
nextcloudals Scope, Back-Channel-Logout unterstützt. - Kompletter Ablauf Login → Code → Token-Endpunkt: ID-Token enthält
sub(UUID),email,name,user_id,quota,groups. - Falsches Client-Secret wird abgelehnt, eine nicht eingetragene Redirect-URI ebenfalls.
- Nicht getestet: Zusammenspiel mit einer echten Nextcloud (App-Einstellungen, Gruppen-Provisioning) und der Back-Channel-Logout.
Blueprints anwenden und prüfen
Authentik überwacht /blueprints (hier: ./blueprints → /blueprints/custom, nur lesbar) rekursiv:
neue oder geänderte *.yaml-Dateien werden automatisch angewendet, zusätzlich läuft jede Stunde ein Abgleich.
Ein Blueprint wird in einer Transaktion angewendet – bei einem Fehler bleibt alles unverändert.
Die Blueprints sind idempotent (Objekte werden über identifiers wiedergefunden) und überschreiben
manuelle Änderungen in der GUI beim nächsten Anwenden.
Prüfen:
- Admin-Oberfläche → Anpassung → Blueprints (Menü kann je nach Version leicht abweichend heißen):
Status pro Blueprint (
successful/error) und Zeitpunkt der letzten Anwendung. - Logs:
docker compose logs worker | grep -i blueprint - Bei Fehler: Meldung dort lesen (meist Tippfehler in Feldnamen oder ein fehlendes Objekt bei
!Find), Datei korrigieren, pushen – oder in der Oberfläche beim Blueprint auf „Anwenden“ klicken. - Vor dem Push:
./scripts/validate.sh(yamllint, YAML-/Tag-Prüfung, Prüfung gegen das offizielle Schema der Version,docker compose config).
CI/CD (Gitea Actions)
validate.yml(Push und Pull Request):./scripts/validate.sh. Der Runner braucht Docker mit Compose-Plugin und Internetzugang (goauthentik.iofür das Schema).deploy.yml(nurmaster): erst Validierung, dann per SSH:git pull --ff-only,docker compose pull,docker compose up -d. Die.envwird nie aus der CI geschrieben.
Gitea-Secrets: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_PATH, DEPLOY_KNOWN_HOSTS
(Ausgabe von ssh-keyscan -p <port> <host>), optional DEPLOY_PORT.
Auf dem Server muss das Repo mit einem lesenden Zugang (z. B. Deploy-Key) geklont sein und .env existieren.
Update von Authentik
AUTHENTIK_TAG in .env.example und in der .env auf dem Server erhöhen, vorher die
Release Notes lesen (nur eine Minor-Version auf einmal),
Backup der Datenbank machen, dann deployen.
Datenhaltung
- Nutzerdaten und Nutzer-Attribute liegen unverschlüsselt in PostgreSQL (Volume
database). - Sensible Fachdaten (z. B. Bankverbindungen, Adressen über das Nötigste hinaus) gehören nicht in
Authentik, sondern in die Fachanwendung, verknüpft über die Mitgliedsnummer (
mitgliedsnummer). - Empfehlung: Verschlüsselung at rest auf Host-Ebene (LUKS) für die Platte mit dem Docker-Datenverzeichnis und den Backups.
- Backups:
docker compose exec -T postgresql pg_dump -U authentik authentik | gzip > backup.sql.gz(verschlüsselt ablegen) und.envseparat im Passwortmanager sichern.
Geplant
Noch nicht umgesetzt, nur dokumentiert:
- Import bestehender Mitglieder samt Passwort-Hashes über die Authentik-API, damit niemand sein Passwort
neu setzen muss.
- Authentik speichert Passwörter im Django-Hash-Format (
<algorithmus>$<parameter>$<hash>). - Bcrypt-Hashes aus PHP/Yii2 beginnen mit
$2y$; Django erwartet$2b$. Beim Import das Präfix$2y→$2bumschreiben und im Django-Format ablegen (bcrypt$$2b$…). - Dafür muss der passende Hasher in Authentik aktiviert sein (Django-
PASSWORD_HASHERS); der Hash sollte beim ersten erfolgreichen Login auf den Standard-Hasher hochgestuft werden (Django-Verhalten, für Authentik zu verifizieren). - Hinweis: Das Benutzermodell kennt in 2026.8.4 ein Attribut
password_hash(der mitgelieferte Bootstrap-Blueprint nutzt es fürAUTHENTIK_BOOTSTRAP_PASSWORD_HASH). Das ist der erste Anlaufpunkt für den Import. - Offen/zu prüfen vor der Umsetzung: ob die API in 2026.8 ein fertiges Hash-Feld beim Anlegen akzeptiert und wie der Hasher aktiviert wird.
- Authentik speichert Passwörter im Django-Hash-Format (