Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Sicherheit Schwierigkeit: Profi

Authentik Teil 2: Apps anbinden – Forward-Auth & OIDC

Dienste an Authentik anbinden: Forward-Auth mit Traefik für Apps ohne Login und OpenID Connect für alles mit eigener SSO-Unterstützung – jeder Schritt geprüft.

· 12 Min. Lesezeit ·Dauer: ca. 75 Minuten
Inhaltsverzeichnis

Authentik läuft – jetzt kommt der Teil, der den Aufwand rechtfertigt: die Dienste dahinter. Es gibt genau zwei Wege, und die Wahl hängt allein daran, ob die Anwendung selbst SSO versteht.

Was bauen wir?

Wir binden zwei Dienste an, jeweils auf dem passenden Weg:

  • Forward-Auth für eine Anwendung ohne eigene Anmeldung. Traefik fragt vor jeder Anfrage bei Authentik nach, ob der Besucher angemeldet ist. Ist er es nicht, landet er auf der Authentik-Anmeldeseite; ist er es, bekommt die Anwendung seine Identität als HTTP-Header.
  • OpenID Connect (OIDC) für Anwendungen mit SSO-Unterstützung – Grafana, Nextcloud, Forgejo, Immich und viele andere. Dort trägst du Client-ID, Secret und die Endpunkte ein, den Rest macht die App selbst.

Alles unten wurde mit Authentik 2026.8 hinter Traefik v3.7 auf Debian 13 durchgespielt: Am Ende zeigt der Testdienst die durchgereichten Identitäts-Header, und das OIDC-Discovery-Dokument liefert echte Endpunkte.

Welcher Weg für welchen Dienst?

Die Entscheidung ist fast immer eindeutig:

DienstUnterstützt OIDC?Weg
Nextcloud, Grafana, Forgejo, Immich, PaperlessjaOIDC – echte Konten, Rollen aus Gruppen
Uptime Kuma, Dockge, Netdata, ein statisches DashboardneinForward-Auth
Metrik-Endpunkte, /actuator, interne APIsirrelevantForward-Auth oder einfacher: nicht öffentlich (VPN)
Dienste mit Mobil-App (Immich, Nextcloud)janur OIDC – Forward-Auth bricht die Apps (siehe unten)

Kurz: OIDC, wo möglich; Forward-Auth, wo nötig. OIDC gibt der Anwendung ein echtes Benutzerkonto inklusive Rollen; Forward-Auth stellt nur eine Tür davor und schiebt einen Namen im Header durch.

Voraussetzungen

  • Ein laufendes Authentik aus Teil 1, erreichbar unter https://DEINE_DOMAIN
  • Ein Traefik v3 mit proxy-Netz (Anleitung) – wir arbeiten hier viel mit Middlewares, die Grundlagen dazu stehen in Traefik härten
  • Ein Dienst zum Üben. Wir nehmen traefik/whoami, weil er alle empfangenen Header ausgibt – damit ist sofort sichtbar, ob die Anbindung wirklich greift

Ein oder zwei Domains?

Wir zeigen den Aufbau auf einer Domain: Authentik unter https://DEINE_DOMAIN/, der geschützte Dienst unter https://DEINE_DOMAIN/whoami. Das ist die sparsame Variante und genau so getestet. Üblicher ist die Trennung (auth.DEINE_DOMAIN für Authentik, app.DEINE_DOMAIN für den Dienst) – die Schritte sind identisch, nur external_host und die Traefik-Regeln tragen dann den anderen Hostnamen.

Schritt für Schritt

1. Zugang für die Automatisierung: ein API-Token

Provider und Anwendungen lassen sich vollständig über die API anlegen – praktisch, weil du damit reproduzierbar arbeitest statt zu klicken. Dafür braucht Authentik einen Token.

Weil du in Teil 1 den Einrichtungsassistenten durchlaufen hast, existiert akadmin bereits. Den Token legst du deshalb in der Oberfläche an: Verzeichnis → Tokens und App-Passwörter → Token erstellen. Als Bezeichner nimm etwas Sprechendes (api-setup), als Absicht API, als Benutzer akadmin. Nach dem Speichern kopierst du den Schlüssel über das Kopiersymbol – er wird nur einmal angezeigt.

Bei einer Neuinstallation geht es auch automatisch

Setzt du Authentik neu auf, kannst du Konto und Token beim allerersten Start erzeugen lassen – ohne Assistenten. Die Variablen gehören an den worker-Dienst (nicht an server) und werden nur beim ersten Start gelesen; bei einer bestehenden Installation bleiben sie wirkungslos. Statt des Klartext-Passworts empfiehlt Authentik inzwischen einen Hash. Das Passwort nimmt hash_password nicht als Argument – hash_password DEIN_PASSWORT bricht mit unrecognized arguments ab –, sondern über eine Eingabeaufforderung oder die Standardeingabe:

Terminal
docker compose run --rm server hash_password

Am Ende steht eine Zeile, die mit pbkdf2_sha256$ beginnt. Sie kommt als AK_BOOTSTRAP_PASSWORD_HASH in die .env, der Token als AK_BOOTSTRAP_TOKEN (zum Beispiel aus openssl rand -hex 32). In der compose.yaml sieht der worker dann so aus:

YAML
  worker:
    environment:
      AUTHENTIK_BOOTSTRAP_PASSWORD_HASH: ${AK_BOOTSTRAP_PASSWORD_HASH}
      AUTHENTIK_BOOTSTRAP_TOKEN: ${AK_BOOTSTRAP_TOKEN}
      AUTHENTIK_BOOTSTRAP_EMAIL: du@DEINE_DOMAIN

Der Hash enthält $-Zeichen: in der .env in einfache Anführungszeichen setzen, direkt in der compose.yaml jedes $ als $$ schreiben.

Prüfe den Token – egal auf welchem Weg er entstanden ist:

Terminal
curl -s -H "Authorization: Bearer DEIN_LANGER_ZUFALLS_TOKEN" \
  https://DEINE_DOMAIN/api/v3/core/users/me/
Ausgabe
{"user":{"username":"akadmin", … "is_superuser":true, …}}

Achtung

Der Bootstrap-Token ist ein vollwertiger Admin-Zugang ohne zweiten Faktor. Er gehört in die .env mit chmod 600, nicht in ein Skript im Repository – und wenn du ihn nur zum Einrichten brauchst, lösch ihn danach unter Verzeichnis → Tokens.

2. Weg A: Forward-Auth für Dienste ohne Login

Der Proxy-Provider ist Authentiks Antwort auf „diese App hat gar keine Benutzerverwaltung". Er kommt in mehreren Betriebsarten; wir nehmen forward_single: eine Anwendung, ein Provider.

Zuerst die IDs der beiden Standard-Flows (aus Teil 1 bekannt):

Terminal
API=https://DEINE_DOMAIN/api/v3
TOK="Authorization: Bearer DEIN_LANGER_ZUFALLS_TOKEN"

AUTHZ=$(curl -s -H "$TOK" "$API/flows/instances/?slug=default-provider-authorization-implicit-consent" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['results'][0]['pk'])")
INVAL=$(curl -s -H "$TOK" "$API/flows/instances/?slug=default-provider-invalidation-flow" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['results'][0]['pk'])")

Dann den Provider:

Terminal
curl -s -X POST -H "$TOK" -H "content-type: application/json" "$API/providers/proxy/" -d "{
  \"name\": \"whoami-forwardauth\",
  \"authorization_flow\": \"$AUTHZ\",
  \"invalidation_flow\": \"$INVAL\",
  \"mode\": \"forward_single\",
  \"external_host\": \"https://DEINE_DOMAIN\"
}"

Und die Anwendung, die ihn benutzt – sie erzeugt auch die Kachel in der Benutzer-Bibliothek:

Terminal
curl -s -X POST -H "$TOK" -H "content-type: application/json" "$API/core/applications/" -d '{
  "name": "whoami (geschützt)", "slug": "whoami", "provider": 1
}'

Die 1 ist die pk aus der Antwort des Provider-Aufrufs. Danach stehen beide in der Übersicht:

Die Anwendungsliste in Authentik mit zwei Einträgen: Grafana über einen OAuth2/OpenID-Anbieter und whoami über einen Proxy Provider
Zwei Anwendungen, zwei Wege: OIDC-Anbieter und Proxy Provider

3. Den Outpost scharf schalten – und die häufigste Falle

Ein Proxy-Provider arbeitet nicht allein: Ein Outpost liefert ihn aus. Authentik bringt einen eingebetteten Outpost mit, der im server-Container läuft – du musst ihm den Provider nur zuweisen:

Terminal
OUT=$(curl -s -H "$TOK" "$API/outposts/instances/" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['results'][0]['pk'])")
curl -s -X PATCH -H "$TOK" -H "content-type: application/json" \
  "$API/outposts/instances/$OUT/" -d '{"providers": [1]}'

Ohne authentik_host landen deine Nutzer auf localhost

Der eingebettete Outpost kennt seine öffentliche Adresse nicht von selbst. Im Test war das Feld authentik_host leer, und die Weiterleitung eines nicht angemeldeten Besuchers ging auf http://localhost/application/o/authorize/… – für jeden Nutzer eine tote Seite. Einmal setzen, dann stimmt sie:

Terminal
curl -s -X PATCH -H "$TOK" -H "content-type: application/json" \
  "$API/outposts/instances/$OUT/" \
  -d '{"config": {"authentik_host": "https://DEINE_DOMAIN/"}}'

Danach zeigt die Weiterleitung auf https://DEINE_DOMAIN/application/o/authorize/… – vorher und nachher nachgemessen. In der Oberfläche liegt dasselbe Feld unter Anwendungen → Outposts → Bearbeiten.

4. Traefik: die Forward-Auth-Middleware

Jetzt fehlt die Verbindung. Traefik fragt vor jeder Anfrage beim Outpost nach – das macht die Middleware forwardauth. Am geschützten Dienst:

YAML
  whoami:
    image: traefik/whoami:v1.12
    restart: unless-stopped
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`DEINE_DOMAIN`) && PathPrefix(`/whoami`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=le"
      - "traefik.http.routers.whoami.middlewares=ak-auth"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"
      - "traefik.http.middlewares.ak-auth.forwardauth.address=http://server:9000/outpost.goauthentik.io/auth/traefik"
      - "traefik.http.middlewares.ak-auth.forwardauth.trustForwardHeader=true"
      - "traefik.http.middlewares.ak-auth.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"

Die drei entscheidenden Zeilen:

  • address zeigt auf den Outpost – hier über den Container-Namen server im gemeinsamen Docker-Netz, also ohne Umweg über das Internet. Der Pfad /outpost.goauthentik.io/auth/traefik ist der Traefik-spezifische Prüf-Endpunkt.
  • trustForwardHeader=true gibt die X-Forwarded-*-Header weiter, damit Authentik die ursprünglich aufgerufene Adresse kennt und korrekt zurückleiten kann.
  • authResponseHeaders ist der eigentliche Nutzen: Diese Header kopiert Traefik aus der Antwort von Authentik in die Anfrage an deine Anwendung. Fehlt die Zeile, ist der Dienst geschützt, weiß aber nicht, wer da ist.

5. Prüfen, dass es wirklich schützt

Erst ohne Anmeldung – dafür reicht curl:

Terminal
curl -s -o /dev/null -w "%{http_code} → %{redirect_url}\n" https://DEINE_DOMAIN/whoami
Ausgabe
302 → https://DEINE_DOMAIN/application/o/authorize/?client_id=…&redirect_uri=…%2Foutpost.goauthentik.io%2Fcallback…

Ein 302 auf die Authentik-Anmeldung ist das gewünschte Ergebnis. Im Browser erscheint die Anmeldeseite vor dem Dienst:

Die Authentik-Anmeldeseite, die beim Aufruf des geschützten Dienstes erscheint
Nicht angemeldet: Authentik fragt, bevor der Dienst antwortet

Nach der Anmeldung landest du wieder auf /whoami – und der Dienst zeigt, was bei ihm ankommt:

Ausgabe
GET /whoami HTTP/1.1
Host: DEINE_DOMAIN
X-Authentik-Email: du@DEINE_DOMAIN
X-Authentik-Groups: authentik Admins
X-Authentik-Name: authentik Default Admin
X-Authentik-Uid: d216e191a6f972c39931a005a7998a68…
X-Authentik-Username: akadmin
X-Forwarded-Proto: https

Das ist der ganze Trick: Die Anwendung hat keine Zeile Anmeldecode – und weiß trotzdem, wer vor ihr sitzt. Dienste, die Header-Authentifizierung unterstützen (Grafana mit auth.proxy, Paperless, Forgejo mit Reverse-Proxy-Auth), können daraus direkt eine Sitzung machen.

Header sind nur so sicher wie ihr Weg

Wenn eine Anwendung X-Authentik-Username vertraut, darf dieser Header niemals von außen kommen können. Deshalb: Der Dienst ist ausschließlich über Traefik erreichbar (kein veröffentlichter Port), und die Middleware hängt an jedem Router, der zu ihm führt. Ein zweiter Router ohne Middleware ist eine offene Tür.

6. Die Grenzen von Forward-Auth kennen

Bevor du alles hinter Forward-Auth hängst: Der Mechanismus setzt einen Browser voraus. Eine nicht angemeldete Anfrage bekommt eine 302-Weiterleitung auf ein Anmeldeformular – und genau daran scheitert alles, was kein Browser ist:

  • Mobil-Apps (Nextcloud, Immich, Audiobookshelf) können mit der Weiterleitung nichts anfangen. Sie sehen HTML statt JSON und melden „Server nicht erreichbar". Für solche Dienste ist OIDC der einzige sinnvolle Weg – oder du nimmst die App-Pfade gezielt aus.
  • API-Zugriffe und Skripte (curl, Backup-Jobs, Webhooks) laufen ins Leere. Wo ein Dienst per Token angesprochen wird, muss dieser Pfad an der Middleware vorbei – oder du verlagerst den Schutz auf eine IP-Allowlist.
  • WebDAV und Sync-Clients verhalten sich wie APIs, mit demselben Ergebnis.

Für solche Ausnahmen legst du einen zweiten Router ohne die Auth-Middleware an, der nur den nötigen Pfad bedient – und schützt ihn anders, etwa mit dem Token der Anwendung selbst. Beispiel für einen API-Pfad:

YAML
      - "traefik.http.routers.app-api.rule=Host(`DEINE_DOMAIN`) && PathPrefix(`/api`)"
      - "traefik.http.routers.app-api.entrypoints=websecure"
      - "traefik.http.routers.app-api.tls.certresolver=le"
      - "traefik.http.routers.app-api.middlewares=rate@file"

Achtung

Jeder solche Ausnahme-Router ist eine bewusste Lücke in deiner Absicherung. Halte den Pfad so eng wie möglich (/api, nicht /), und schreib dir auf, warum er existiert – in einem halben Jahr weiß das sonst niemand mehr.

Zur Vollständigkeit die dritte Betriebsart des Proxy-Providers: forward_domain. Damit bedient ein Outpost alle Dienste einer Domain (*.DEINE_DOMAIN), und Nutzer melden sich einmal für alle an. Das ist der bequemere Aufbau ab etwa drei Diensten, verlangt aber, dass Authentik und alle Anwendungen unter derselben übergeordneten Domain liegen, damit das Sitzungs-Cookie geteilt werden kann. Hier getestet ist forward_single – die Umstellung betrifft nur den Modus im Provider und die Cookie-Domain.

7. Weg B: OpenID Connect für Dienste mit eigenem SSO

Hat die Anwendung eine OIDC-Anmeldung, ist das der bessere Weg: Sie führt dann echte Benutzerkonten inklusive Rollen, statt nur einen Namen im Header zu sehen.

Provider anlegen – wichtig ist die Redirect-URI, die genau der Rückadresse der Anwendung entsprechen muss (Beispiel Grafana):

Terminal
curl -s -X POST -H "$TOK" -H "content-type: application/json" "$API/providers/oauth2/" -d "{
  \"name\": \"grafana-oidc\",
  \"authorization_flow\": \"$AUTHZ\",
  \"invalidation_flow\": \"$INVAL\",
  \"client_type\": \"confidential\",
  \"redirect_uris\": [{\"matching_mode\": \"strict\", \"url\": \"https://GRAFANA_DOMAIN/login/generic_oauth\"}]
}"

Die Antwort enthält client_id und client_secret – beide brauchst du in der Anwendung. Danach wie oben eine Anwendung anlegen, die auf diesen Provider zeigt.

Ob alles sitzt, verrät das Discovery-Dokument. Authentik veröffentlicht es pro Anwendung unter deren Slug:

Terminal
curl -s https://DEINE_DOMAIN/application/o/grafana/.well-known/openid-configuration \
  | python3 -m json.tool | head -12
Ausgabe
"issuer": "https://DEINE_DOMAIN/application/o/grafana/",
"authorization_endpoint": "https://DEINE_DOMAIN/application/o/authorize/",
"token_endpoint": "https://DEINE_DOMAIN/application/o/token/",
"userinfo_endpoint": "https://DEINE_DOMAIN/application/o/userinfo/",

Beachte: Der Issuer ist anwendungsspezifisch (er enthält den Slug), die übrigen Endpunkte sind für alle Anwendungen gleich. Genau diese Adressen – oder die Discovery-URL selbst, wenn die App sie unterstützt – trägst du in der Anwendung ein. Für Grafana etwa:

INI
GF_AUTH_GENERIC_OAUTH_ENABLED=true
GF_AUTH_GENERIC_OAUTH_NAME=Authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID=DEINE_CLIENT_ID
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=DEIN_CLIENT_SECRET
GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://DEINE_DOMAIN/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://DEINE_DOMAIN/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL=https://DEINE_DOMAIN/application/o/userinfo/

Gruppen als Rollen

Die meisten Anwendungen können aus einem Feld im Token Rollen ableiten – Grafana etwa über role_attribute_path. Damit steuerst du Admin-Rechte zentral über Authentik-Gruppen, statt sie in jeder App zu pflegen. Genau deshalb der Rat aus Teil 1: Berechtigungen an Gruppen hängen, nicht an einzelne Benutzer.

8. Wer darf was? Zugriff einschränken

Solange an einer Anwendung keine Richtlinie hängt, darf sie jeder angemeldete Benutzer nutzen. Für den Regelfall bindest du eine Gruppe an die Anwendung: Anwendungen → deine Anwendung → Richtlinien / Gruppen / Benutzer-Bindungen, dort die Gruppe auswählen.

Erst danach verschwindet die Kachel in der Benutzer-Bibliothek für alle anderen – und der Zugriff ist gesperrt. Prüfen lässt sich das am ehrlichsten mit einem zweiten Testkonto, das nicht in der Gruppe ist: Es soll die Anwendung weder sehen noch aufrufen können.

Wenn es nicht funktioniert

Das Skript bricht beim Ermitteln der Flows mit IndexError: list index out of range ab. Das passiert nur direkt nach einer frischen Installation: /-/health/ready/ meldet schon 200, während der worker die mitgelieferten Abläufe noch einspielt. Im Test gab es in diesem Moment erst einen Flow, 20 bis 30 Sekunden später alle 15. Kurz warten und neu starten.

Der Aufruf leitet auf http://localhost/application/o/authorize/… weiter. Der eingebettete Outpost kennt seine öffentliche Adresse nicht: Das Feld authentik_host ist leer. Setz es auf https://DEINE_DOMAIN/ (Schritt 3) – vorher landet jeder Nutzer auf einer toten Seite.

Traefik antwortet mit 500 oder es kommt gar keine Weiterleitung. Dann erreicht die Middleware den Outpost nicht. Die address muss aus Traefiks Sicht auflösbar sein – also der Container-Name im gemeinsamen Netz (http://server:9000/outpost.goauthentik.io/auth/traefik), nicht localhost. Prüfe im Traefik-Ordner mit docker compose exec traefik wget -qO- http://server:9000/-/health/live/, ob die Verbindung steht.

Der Dienst ist geschützt, kennt aber den Benutzer nicht. Es fehlt authResponseHeaders an der Middleware. Ohne diese Liste verwirft Traefik die Identitäts-Header aus Authentiks Antwort – die Anmeldung funktioniert, die Anwendung sieht aber nichts.

Nach der Anmeldung landet man in einer Weiterleitungsschleife. Meist passt external_host nicht zur tatsächlich aufgerufenen Adresse (http statt https, mit oder ohne Subdomain). Er muss exakt die URL sein, unter der Nutzer den Dienst aufrufen.

Die OIDC-Anmeldung bricht mit „redirect_uri mismatch" ab. Authentik prüft die Rückadresse strikt. Trag im Provider genau die URI ein, die die Anwendung schickt – bei Grafana etwa https://GRAFANA_DOMAIN/login/generic_oauth. Ein fehlender Schrägstrich reicht zum Scheitern.

Alle angemeldeten Nutzer kommen überall rein. Ohne Bindung an eine Gruppe ist eine Anwendung für jeden angemeldeten Benutzer offen. Häng eine Gruppen-Bindung an die Anwendung (Schritt 8) und prüfe mit einem Konto, das nicht in der Gruppe ist.

Dein Problem ist nicht dabei? Alle Fehlermeldungen durchsuchen →

Wartung & Backups

Nach jedem Authentik-Update stichprobenartig prüfen. Ein Update kann Flows und Provider verändern; die Anbindungen selbst sind robust, aber der schnelle Test lohnt: curl auf den geschützten Pfad muss weiterhin 302 liefern, und das Discovery-Dokument muss deine Endpunkte zeigen. Zwei Kommandos, die verhindern, dass eine Anmeldung wochenlang unbemerkt kaputt ist.

Client-Secrets gehören in den Passwortmanager. Sie stehen in der Anwendung (Umgebungsvariablen) und in Authentik. Wird eines neu erzeugt, muss die andere Seite mitgezogen werden – notiere beim Anlegen, welche App welches Secret nutzt, sonst suchst du später in beiden Systemen.

Die Datenbank ist jetzt noch wichtiger. Provider, Anwendungen und Gruppen-Bindungen liegen alle in PostgreSQL. Der pg_dump aus Teil 1 ist damit kein Nice-to-have mehr: Ohne ihn baust du nach einem Ausfall jede Anbindung neu. Mit Restic täglich wegsichern.

Den Notausgang offen halten. Jeder Dienst hinter Forward-Auth ist nur so erreichbar wie Authentik. Behalte in wichtigen Anwendungen ein lokales Konto – und häng keine Werkzeuge hinter SSO, die du zur Reparatur brauchst; das gilt besonders für Dockge und ähnliche Verwaltungsoberflächen. Ein Monitoring auf /-/health/ready/ merkt den Ausfall vor deinen Nutzern.

Tokens aufräumen. Der Bootstrap-Token aus Schritt 1 ist ein Admin-Zugang ohne zweiten Faktor. Wenn die Einrichtung steht, lösch ihn unter Verzeichnis → Tokens – und leg für spätere Automatisierung einen eigenen Token mit begrenzten Rechten an.

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken