⚡ HomelabVergleich

Single Sign-On selbst gehostet: Authentik, Keycloak und Cloudflare Access im Vergleich

Du kennst das: Jeder Dienst im eigenen Netz hat eigene Login-Seiten, eigene Passwörter, eigene Benutzerdatenbanken. Ein Mitarbeiter verlässt das Unternehmen — und drei Wochen später findest du noch, dass sein Account auf dem internen Wiki, in der Ticketsystem-Oberfläche und im Monitoring noch aktiv ist. Oder du selbst hast auf vier verschiedenen Selfhosted-Services in verschiedenen Passwörtern gesetzt, weil du sie nicht alle auf einmal ändern wolltest.

Single Sign-On (SSO) löst dieses Problem, indem es einen zentralen Identitätsanbieter (Identity Provider, IdP) einführt, gegen den sich alle Dienste richten. Der User meldet sich einmal an — und die Dienste vertrauen dem IdP, wenn er ihnen sagt: „Das ist diese Person, und sie hat sich erfolgreich authentifiziert."

Warum die meisten Selfhoster trotzdem keine SSO einrichten, ist nicht technische Schwierigkeit allein — es ist, dass die meisten Articles entweder zu theoretisch sind (Prozess-Auth-Flows mit Paint-Strichdiagrammen) oder direkt in eine Enterprise-Wolke laufen. Dieser Artikel befasst sich mit dem, was für kleine Firmen (5–50 Mitarbeiter) und technisch interessierte Homelab-Betreiber tatsächlich umsetzbar ist: drei Kandidaten, ein konkretes Docker-Setup, Realitätscheck im Produktivbetrieb.

Was SSO fachlich löst — und was nicht

SSO löst:

SSO löst nicht:

Die drei Kandidaten im Überblick

Für den Anwendungsfall "kleine Firma oder Fortgeschrittener Homelab" kommen drei Lösungen infrage. Die Auswahl fällt nicht nach Feature-Listen, sondern nach den tatsächlichen Bedingungen — wie viele Services müssen verbunden werden, welche Protokolle unterstützen die Dienste OIDC oder SAML, wie viel Wartungsaufwand ist akzeptabel.

| Kriterium | Authentik | Keycloak | Cloudflare Access |

|---|---|---|---|

| Protokolle | OIDC (primär), SAML (über Brokern oder Flow-Editor), LDAP-Integration als Benutzerverzeichnis-Backend | OIDC, SAML (vollständig, inkl. Identity-Brokering), OpenID Connect Dynamic Client Registration | OIDC (für Dienste hinter Access geschützte Web-Applikationen), Cloudflare-eigenes Access-Protokoll fürZTNA; SAML nur in Enterprise-Plänen |

| Benutzerverzeichnis | Eigene Benutzerdatenbank (integriert), LDAP/Active Directory als Backend, SAML-IdP-Brokering | Eigene Benutzerdatenbank, LDAP/AD, Identity-Brokering (andere IdPs als Upstream) | Cloudflare-eigene Identitätsquellen (Email, SSO mit SAML/OIDC von externen IdPs wie Google, Azure AD, Okta); kein eigenes Benutzerdatenbank-Modell |

| Lizenzkosten | Open Source (MIT), keine Server-Lizenzen. Cloudflare-übertragene Zugriffsschicht nicht enthalten. | Open Source (Apache 2.0) — Keycloak selbst kostenlos. Red Hat entwickelt Keycloak als Open-Source-Projekt weiter; einige Enterprise-Features (z.B. erweiterte Reporting, bestimmte Automatisierungen) können künftig in kommerziellen Varianten landen | Free-Plan (bis zu 50 Nutzer, begrenzte ZTNA-Funktionen), danach ab ~3 €/Nutzer/Monat für Paid-Pläne. Keine eigene Server-Infrastruktur nötig. |

| Hardwarebedarf | Mindestens 2 CPU-Kerne, 2 GB RAM für den produktiven Betrieb (PostgreSQL + Authentik-App + Worker). Für kleine Homelab auf einem 4-Core-System mit 8 GB RAM machbar. | Mindestens 2 GB RAM für den Keycloak-Server, 4 GB empfohlen für Produktivumgebungen mit vielenRealm-Entrypoints. Java-Laufzeitumgebung braucht Overhead. Günstig auf einem dedizierten Rechner mit 4+ GB freiem RAM. | Kein eigener Server — Cloudflare-infrastruktur. Client-Seite ein Cloudflare-Tunnel (cloudflared) oder ein Access-Gateway, das auf dem eigenen Netz läuft; das ist ressourcenschonend (ein paar MB RAM, kein spezialisiertes Hardware). |

| Bedienaufwand | Mittel: OIDC-Client-Konfiguration im Web-UI, aber die Einrichtung ist mit klaren Konzepten (Application → OIDC-Client → Credentials) dokumentiert. Benutzerverwaltung über Web-UI oder API. | Hoch: Keycloak hat ein sehr breites Feature-Set und eine komplexere Oberfläche. Realms, Clients, Roles, Groups, Identity-Brokering, User-Federation — das Konzeptmodell ist mächtig, aber die Einarbeitung dauert. Für einfache OIDC-Integration reicht der Funnel aber. | Niedrig: Einrichtung über das Cloudflare-Dashboard. Der Access-Policy-Generator ist GUI-getrieben, keine Serverwartung. Aber: Abhängigkeit von Cloudflare, Domain muss bei Cloudflare eingetragen sein. |

| Eignung für Firmen | Gut für Firmen, die volle Eigenkontrolle über ihre Identitätsdaten und -protokolle wollen und bereit sind, den IdP selbst zu betreiben. OIDC-Integration läuft in jeder modernen Webframework gut. SAML über Flow-Editor erreichbar, aber zusätzlicher Aufwand. | Gut für Firmen mit komplexeren Anforderungen (mehrere Realms für Sub-Marken/Bereiche, Identity-Brokering zu externen IdPs, SAML-schwergewichtige Legacy-Anwendungen, erweiterte Rollen-Engine). Höhere Einrichtungskosten, höhere Flexibilität. | Gut für Firmen, die keine eigene IdP-Server-Infrastruktur betreiben wollen und die Domain bereits bei Cloudflare haben. Cloudflare Access als Identity-Aware Proxy ist eine pragmatische Wahl für Web-Dienste, weniger für Machine-to-Machine-Integrationen. |

Abgrenzung zu den anderen BsN-Artikeln

Dieser Artikel behandelt nur die Authentifizierungsschicht — wer darf auf welchen Dienst zugreifen und wie wird diese Frage zentral entschieden. Die Artikel sicherer-fernzugriff.md (Tailscale, WireGuard) und docker-vs-vm.md behandeln andere Schichten:

Vollständige Authentik-Anleitung mit OIDC

Authentik ist der Kandidat mit dem günstigsten Einstieg: Docker-basiert, Web-UI klar strukturiert, OIDC-default, keine Java-Laufzeitumgebung. Die folgende Anleitung richtet ein minimales, produktionsnahes Authentik-Setup ein und verbindet ein Beispiel-Python-Service als OIDC-Client.

Voraussetzungen

Schritt 1: Das grundlegende docker-compose-Grundgerüst

Erstelle ein Verzeichnis für die Authentik-Instanz:


mkdir -p ~/authentik/{data,media}
cd ~/authentik

Erstelle docker-compose.yml mit den folgenden Inhalten. Das Setup besteht aus drei Diensten: PostgreSQL als Datenbank, Redis als Caching/Session-Backend, und Authentik selbst.


version: "3.8"

services:
  # PostgreSQL-Datenbank für Authentik
  postgres:
    image: docker.io/postgres:16-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U authentik"]
      interval: 10s
      timeout: 5s
      retries: 3
    environment:
      POSTGRES_USER: authentik
      POSTGRES_PASSWORD: ${PG_PASSWORD}
      POSTGRES_DB: authentik
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - authentik-net

  # Redis für Caching und Session-Speicherung
  redis:
    image: docker.io/redis:7-alpine
    restart: unless-stopped
    command: redis-server --save "" --appendonly "no"
    networks:
      - authentik-net

  # Authentik-Applikation
  authentik:
    image: docker.io/authentik/authentik:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      # Datenbank-Verbindung — die Werte kommen aus der .env-Datei (siehe Schritt 2)
      AUTHENTIK_DB_HOST: postgres
      AUTHENTIK_DB_USER: authentik
      AUTHENTIK_DB_PASSWORD: ${PG_PASSWORD}
      AUTHENTIK_DB_NAME: authentik
      AUTHENTIK_REDIS__HOST: redis

      # URL unter der Authentik erreichbar ist — hier über den Reverse-Proxy mit TLS
      AUTHENTIK_URL: https://auth.brillianze.de

      # Secret für Session-Cookies und interne Verschlüsselung — zufälliger Wert,}|=32 Zeichen
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET}

      # Admin-Konto: Name, E-Mail, Passwort werden bei erster Initialisierung erstellt
      # Das wird im nächsten Schritt über ein CLI-Kommando gesetzt
    volumes:
      - ./media:/media
      - ./data:/data
    networks:
      - authentik-net

networks:
  authentik-net:
    driver: bridge

Erstelle eine .env-Datei im selben Verzeichnis mit den konkreten Werten (Passwörter und Secrets nie in Version Control):


# PostgreSQL-Passwort für den authentik-DB-Benutzer
PG_PASSWORD=$(openssl rand -hex 24)

# Authentik internes Secret Key (für Cookie-Verschlüsselung, JWT-Signierung)
AUTHENTIK_SECRET=$(openssl rand -hex 32)

# Diese beiden Variablen schreiben wir in .env, damit docker compose sie liest
echo "PG_PASSWORD=$PG_PASSWORD" > .env
echo "AUTHENTIK_SECRET=$AUTHENTIK_SECRET" >> .env

Anmerkung: In einer produktiven Umgebung solltest du diese Werte in einem Secrets-Manager oder zumindest in einer passwortgeschützten Datei ablegen. Die .env-Datei gehört nicht ins Git-Repository der Homelab-Konfiguration.

Starte die Infrastruktur:


docker compose up -d

Warte, bis die PostgreSQL-Instanz gesund ist (das depends_on mit condition: service_healthy sorgt dafür, dass Authentik nicht vor der Datenbank startet):


docker compose logs -f postgres

Sobald du database system is ready to accept connections siehst, kannst du das Frontend überwachen:


docker compose logs -f authentik

Schritt 2: Admin-Benutzer einrichten

Authentik bringt nach dem ersten Start keine vordefinierten Benutzer mit. Du erstellst den ersten Admin-Benutzer über die Authentik-Kommandozeilen-Schnittstelle (CLI) im Container:


# Container-Name "authentik-authentik-1" entsteht aus dem Verzeichnisnamen des Compose-Projekts;
# prüfe mit "docker ps" den exakten Namen oder nutze "docker compose exec authentik ..."

docker compose exec authentik authentik createuser \
  --username admin \
  --email bsndemo@brillianze.de \
  --password "EinSehrStarkesPasswortDasNieWiederholtWird" \
  --is-superuser \
  --autocomplete

Hinweis: Das --autocomplete-Flag sagt Authentik, den Benutzer sofort zu aktivieren und das Passwort im Klartext nicht wieder zu speichern (es wird gehasht). In der Produktionsumgebung wählst du ein Passwort, das du in einem Passwort-Manager ablegen — und gibst es nicht in Chat-Protokolle, Terminal-History oder Konfigurationsdateien.

Nach dem Erstellen kannst du dich an der Web-Oberfläche anmelden unter der URL, die du in AUTHENTIK_URL eingetragen hast. Beim ersten Login fragt Authentik ggf. nach einer Verifizierung über E-Mail — das hängt von deinen SMTP-Einstellungen ab (in der Web-Oberfläche unter Einstellungen → Email).

Schritt 3: Einen OIDC-Client anlegen

Ein OIDC-Client repräsentiert den Dienst, der sich gegen Authentik authentifizieren will. Du erstellst ihn in der Web-Oberfläche:

1. Öffne die Authentik-Web-Oberfläche (die AUTHENTIK_URL) und melde dich mit dem Admin-Benutzer an.

2. Gehe zu Anwendungen → OIDC-Client (oder, je nach Version, zu Applications → + Anwendung hinzufügen → OIDC-Client).

3. Erstelle einen neuen Client mit folgenden Eigenschaften:

- Name: Ein interner Bezeichner, z.B. python-beispiel-service

- Client ID: Ein eindeutiger Identifier, z.B. python-exampleservice-oidc

- Client Secret: Authentik generiert einen hier; kopiere ihn — du brauchst ihn später im Python-Service

- Redirect URIs: Die URL, an die Authentik den User nach erfolgreichem Login weiterleiten soll, z.B. https://python-service.brillianze.de/callback

- Grant Types: Mindestens Authorization Code für Web-Applikationen. Refresh Token aktivieren, damit der Service Tokens frisch halten kann, ohne den User immer wieder um Einwilligung zu bitten.

- Response Types: code (Authorization Code Flow — der sichere Standard für Web-Applikationen)

Speichere den Client und notiere dir:

Die exakten Endpunkt-URLs findest du in der OIDC-Client-Konfiguration oder unter Einstellungen → Identity Providers → OIDC → Endpoints. Die <issuer-id>-Platzhalter müssen durch die reale ID ersetzt werden, die Authentik für den OIDC-Provider vergibt.

Schritt 4: Beispiel-Python-Service mit OIDC schützen

Der Beispiel-Service ist eine minimale Flask-Applikation, die zwei Dinge tut: Sie zeigt eine geschützte Seite, wenn ein gültiger Access-Token vorliegt (der vom IdP ausgestellt wurde), und sie führt den OAuth2-Authorization-Code-Flow aus, um den Token vom Authentik-IdP zu erhalten.

Die Beispiel-App nutzt authlib (Python-Bibliothek für OAuth2/OIDC) in der Version 1.x. Sie führt nicht den eigenen Cryptographic-Priming durch — Authlib kümmert sich um Token-Exchange, State-Parameter, PKCE (wenn aktiviert).

requirements.txt:


Flask==3.1.0
authlib==1.3.2
requests==2.32.3

app.py:


"""
Beispiel-Python-Service mit OIDC gegen Authentik.
Dies ist ein minimales Beispiel — in einer produktiven Applikation
solltest du Sessions sicherer verwalten, HTTPS erzwingen,
Tokens nicht im Klartext loggen und PKCE aktivieren (authentik unterstützt es).
"""
import os
from flask import Flask, redirect, session, url_for, jsonify, request
from authlib.integrations.flask_client import OAuth

app = Flask(__name__)
# Flask-Session Secret — separat vom Authentik Secret Key; eigenes Zufallspasswort
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "bitte-aendern")

# OIDC-Konfiguration aus Umgebungsvariablen — in Produktion durch Konfigurationsmanagement ersetzen
AUTHTIK_BASE_URL = os.environ.get("AUTHTIK_BASE_URL", "https://auth.brillianze.de")
OIDC_CLIENT_ID = os.environ.get("OIDC_CLIENT_ID", "python-exampleservice-oidc")
OIDC_CLIENT_SECRET = os.environ.get("OIDC_CLIENT_SECRET", "")
OIDC_REDIRECT_URI = os.environ.get("OIDC_REDIRECT_URI", "https://python-service.brillianze.de/callback")

# OAuth-Client initialisieren
oauth = OAuth(app)

# Den OIDC-Provider registrieren — Authlib nutzt die Discovery-URL, um
# Authorization-Endpunkt, Token-Endpunkt usw. automatisch zu laden
oauth.register(
    name="authentik",
    server_metadata_url=f"{AUTHTIK_BASE_URL}/api/v3/providers/oidc/<issuer-id>/.well-known/openid-configuration",
    client_id=OIDC_CLIENT_ID,
    client_secret=OIDC_CLIENT_SECRET,
    access_token_url=f"{AUTHTIK_BASE_URL}/api/v3/providers/oidc/<issuer-id>/token",
    authorize_url=f"{AUTHTIK_BASE_URL}/api/v3/providers/oidc/<issuer-id>/authorize",
    client_kw={\"token_endpoint_auth_method\": \"client_secret_post\"},
    # PKCE aktivieren: authentik unterstützt S256; CSRF-Schutz im Authorization Code Flow
    pkce="S256",
)

@app.route("/")
def index():
    """Startseite: Wenn der Benutzer nicht authentifiziert ist, zum Login weiterleiten."""
    if "user" not in session:
        return redirect(url_for("login"))
    return """
    <h1>Beispiel-Python-Service — geschützt mit OIDC</h1>
    <p>Du bist angemeldet als: <b>{}</b></p>
    <p>Access-Token (nicht im Produktivbetrieb im HTML ausgeben!): <code>{}</code></p>
    <p><a href="/logout">Abmelden</a></p>
    """.format(
        session.get("user", {}).get("email", "unbekannt"),
        session.get("access_token")[:20] + "..." if session.get("access_token") else "keiner",
    )

@app.route("/login")
def login():
    """Authentifizierung starten: Authorization Code Flow an Authentik."""
    # Redirect URI muss exakt mit dem in Authentik konfigurierten Redirect URI übereinstimmen
    redirect_uri = url_for("callback", _external=True)
    return oauth.authentik.authorize_redirect(
        redirect_uri=redirect_uri,
        scope="openid profile email",
    )

@app.route("/callback")
def callback():
    """Authentik hat den User zurückgeleitet und einen Code übergeben — Token austauschen."""
    try:
        token = oauth.authentik.authorize_access_token()
    except Exception as exc:
        # In Produktion: detaillierte Fehlerbehandlung, Logging, keine Stack-Traces an Endnutzer
        return "Authentifizierungsfehler: {}".format(exc), 401

    # Userinfo vom IdP anfordern — enthält claims wie email, name
    userinfo = oauth.authentik.userinfo(token=token)
    session["user"] = userinfo
    session["access_token"] = token.get("access_token", "")

    return redirect(url_for("index"))

@app.route("/logout")
def logout():
    """Session löschen — keine Verbindung zum IdP (Local Logout)."""
    session.clear()
    return redirect(url_for("index"))

if __name__ == "__main__":
    # Entwicklung nur: Flask-Built-in-Server über HTTP. In Produktion: WSGI-Server (Gunicorn)
    # hinter Reverse Proxy mit TLS
    app.run(host="0.0.0.0", port=5000, debug=False)

Wichtige Hinweise zum Beispiel:

Schritt 5: Den Token-Ausstellungsprozess mit curl testen

Bevor du den Python-Service deployst, ist es sinnvoll, den OAuth2-Authorization-Code-Flow manuell mit curl zu verifizieren. Das hilft, Misskonfigurationen (falsche Redirect URIs, falsche Scopes, falsch generierte Secrets) als solche zu erkennen.

Der Authorization-Code-Flow ist zweistufig:

Schritt A: Den Authorization-Link bauen und im Browser öffnen. Der User meldet sich bei Authentik an und Authorisiert den Client. Authentik leitet zurück an den Redirect URI mit einem code-Parameter.

Der Link (zum manuellen Klick im Browser):


https://auth.brillianze.de/api/v3/providers/oidc/<issuer-id>/authorize?
  response_type=code&
  client_id=python-exampleservice-oidc&
  redirect_uri=https://python-service.brillianze.de/callback&
  scope=openid%20profile%20email&
  state=<ein-zufaelliger-state-string>

state ist ein zufälliger String, den du erzeugst und der später im Callback validiert wird (CSRF-Schutz). In der manuellen curl-Test-Phase kannst du state=teststate123 verwenden.

Öffne diesen Link in einem Browser, melde dich mit einem Test-Benutzer (nicht zwingend dem Admin) an, und erlaube den Zugriff. Du landest nach erfolgreicher Authorisierung auf:


https://python-service.brillianze.de/callback?code=<authorization-code>&state=teststate123

Kopiere den code-Wert aus der URL.

Schritt B: Den Authorization Code gegen einen Access-Token austauschen:


# Die Werte aus deiner Authentik-Client-Konfiguration einsetzen
CLIENT_ID="python-exampleservice-oidc"
CLIENT_SECRET="<hier-den-Client-Secret-eintragen>"
REDIRECT_URI="https://python-service.brillianze.de/callback"
ISSUER_ID="<die-issuer-id-aus-authentik>"
CODE="<den-code-aus-dem-browser-aufruf-eintragen>"

curl -X POST "https://auth.brillianze.de/api/v3/providers/oidc/${ISSUER_ID}/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=${CODE}" \
  -d "redirect_uri=${REDIRECT_URI}" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}"

Erwartete Response (JSON):


{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}

Der access_token ist ein JWT (JSON Web Token). Du kannst ihn mit einem JWT-Debugger (offline, lokal — keine Tokens an Online-Debugger schicken) inspizieren, um zu sehen, welche Claims er enthält. Typische Claims in einem Authentik-ID-Token: sub (Subject-ID), email, name, preferred_username, iss (Issuer-URL), aud (Client-ID), exp (Ablaufzeit), iat (Ausgestellt-Zeit).

Validierung des Tokens:

Das JWT ist signiert — Authentik signiert mit einem privaten RSA-Schlüssel, und der öffentliche Schlüssel steht unter der Discovery-URL (jwks_uri). Eine korrekte Validierung würde den öffentlichen Schlüssel laden, die Signatur verifizieren und exp/iss/aud prüfen. Für den Test mit curl reicht es, den Token gegen den Userinfo-Endpunkt zu prüfen:


TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X GET "https://auth.brillianze.de/api/v3/providers/oidc/<issuer-id>/userinfo" \
  -H "Authorization: Bearer ${TOKEN}"

Erwartete Response:


{
  "sub": "abcdef123456",
  "email": "testuser@brillianze.de",
  "email_verified": true,
  "name": "Test Benutzer",
  "preferred_username": "testuser"
}

Wenn die Userinfo-Antwort kommt und sub + email enthält, ist der Token gültig und die OIDC-Integration korrekt konfiguriert. Der Python-Service kann nun denselben Token vom Authorization Code Flow erhalten und gegen den Userinfo-Endpunkt validieren.

Schritt 6: Den Beispiel-Service als Docker-Container starten

Das docker-compose.yml für den Beispiel-Service (separates Projekt oder als zusätzlicher Dienst):


version: "3.8"

services:
  python-beispiel-service:
    image: docker.io/python:3.12-slim
    restart: unless-stopped
    ports:
      - "127.0.0.1:5000:5000"
    environment:
      FLASK_SECRET_KEY: ${FLASK_SECRET}
      AUTHTIK_BASE_URL: https://auth.brillianze.de
      OIDC_CLIENT_ID: python-exampleservice-oidc
      OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      OIDC_REDIRECT_URI: https://python-service.brillianze.de/callback
    volumes:
      - ./app:/app
    command: >
      bash -c "pip install --no-cache-dir -r /app/requirements.txt &&
               gunicorn --bind 0.0.0.0:5000 --workers 2 'app:app'"
    networks:
      - authentik-net

networks:
  authentik-net:
    external: true

Beachte: Der Dienst bindet sich nur an 127.0.0.1, nicht an alle Interfaces. Der Reverse-Proxy (Caddy, Nginx) macht ihn nach außen erreichbar — das ist sicherer als eine direkte Port-Exposition.

Produktivbetrieb: Was du nach derEinrichtung brauchst

TLS über den eigenen Reverse Proxy

Authentik führt in der Standardkonfiguration keine TLS-terminierung selbst durch. Die AUTHENTIK_URL ist die URL, unter der es erreichbar sein soll — aber das Traffik von Clients zu Authentik muss verschlüsselt sein, sonst werden die Cookies (Session, OIDC- Tokens im Browser) unverschlüsselt übertragen.

Ein Caddy-Reverse-Proxy in der BsN-Nutzung (vollständigere Konfiguration in einem separaten Artikel "Interne PKI und Caddy für Selfhosted-Dienste"):


auth.brillianze.de {
  tls /pfad/zu/zertifikat.crt /pfad/zu/private-key.key

  reverse_proxy authentik:80 {
    # Forward das X-Forwarded-Proto, damit Authentik weiß, ob die
    # Client-Verbindung HTTPS war (wichtig für Cookie-Secure-Flag)
    header_up X-Forwarded-Proto {scheme}
    header_up X-Forwarded-For {remote}
  }
}

Authentik selbst besitzt eine Konfigurationsoption PROXY_FIX, die vom Reverse Proxy gesendete Header akzeptiert und für interne URL-Berechnung nutzt. In docker-compose.yml kannst du hinzufügen:


    environment:
      ...
      AUTHENTIK_PROXY_FIX: "true"

Backups der Konfiguration

Authentik speichert seine Konfiguration (Applications, OIDC-Clients, Policies, Benutzer, Flows) in der PostgreSQL-Datenbank. Die media- und data-Volumes enthalten hochgeladene Bilder (z.B. Logos für Anwendungen) und einige Konfigurationsdateien.

Ein Backup-Skript (via cron täglich, z.B. 03:00 Uhr):


#!/bin/bash
# authentik-backup.sh — vereinfachtes Backup für Authentik-Datenbank und Volumes
# In Produktion: verschlüsseln, nach einem separaten Host übertragen, Retention-Policy

BACKUP_DIR="/var/backups/authentik"
DATE=$(date +%Y-%m-%d)
PG_CONTAINER="authentik-postgres-1"
DATA_DIR="/home/bsn/authentik"

mkdir -p "${BACKUP_DIR}/${DATE}"

# Datenbank-Dump über docker exec — der Dokumenten-User ist "authentik"
docker exec "${PG_CONTAINER}" pg_dump -U authentik authentik | \
  gzip > "${BACKUP_DIR}/${DATE}/authentik-db-${DATE}.sql.gz"

# Volumes komprimiert archivieren
tar czf "${BACKUP_DIR}/${DATE}/authentik-media-${DATE}.tar.gz" -C "${DATA_DIR}" media
tar czf "${BACKUP_DIR}/${DATE}/authentik-data-${DATE}.tar.gz" -C "${DATA_DIR}" data

# Alte Backups (älter als 30 Tage) aufräumen
find "${BACKUP_DIR}" -type d -mtime +30 -exec rm -rf {} \;

Das Backup alleine bringt nichts, wenn es nicht getestet wurde — ein Restore-Test (Datenbank in eine Test-Instanz laden, prüfen ob Auth-Flows und Clients funktionieren) sollte mindestens einmal pro Quartal gemacht werden.

Ausfallverhalten wenn der Identity Provider steht

Wenn Authentik (oder der gesamte Server, auf dem es läuft) nicht verfügbar ist, können sich Nutzer nicht mehr anmelden. Was passiert mit bereits angemeldeten Sessions?

Praxisempfehlung: Für wenige kritische Dienste (z.B. das Ticketsystem) kannst du die Access-Token-Lebensdauer entsprechend lang bemessen (z.B. 8 Stunden), Refresh-Token ebenfalls, und machst es den Nutzern so gut wie möglich. Für hochkritische Dienste ist ein kluges Fallback (z.B. lokale Fallback-Authentifizierung mit eigenem Benutzernamens-Verzeichnis, das nur im Notbetrieb aktiviert wird) eine Facility, die enterprise-grade IdPs bieten. Authentik hat keine eingebaute "Notfall-Modus"-Funktion — das müsstest du selbst bauen (z.B. eine gesonderte Anwendung ohne OIDC-Client-Integration in der Produktion, aber mit der gleichen Benutzerdatenbank im Hintergrund über den Authentik-API-Schnittstelle oder direkt über die Datenbank).

Passwortrichtlinien

Authentik hat unter Einstellungen → Passwortrichtlinien grundlegende Regeln: Mindestlänge, Komplexitätsanforderungen (Großbuchstaben, Ziffern, Sonderzeichen), Verbotsliste für häufige Passwörter. Diese Einstellungen gelten für die Passwörter, die Nutzer gegen den IdP selbst setzen — nicht für Service-Accounts oder andere Systeme.

In kleinen Firmen ist eine Praxis, die funktioniert: 12 Zeichen Mindestlänge, keine bekannten Passwörter (Authentik kann Common-Passwort-Listen laden), Protektion gegen Recycling (Passwort darf nicht dem letzten dritten Passwort entsprechen). Für besonders geschützte Accounts (Admin, IdP-Admin) zusätzlich einen Hardware-Schlüssel (YubiKey FIDO2) zu verlangen — Authentik unterstützt WebAuthn (FIDO2) als MFA-Methode, die ist im Enterprise-Kontext einer kleinen Firma eine sinnvolle Investition.

Interne Konten deaktivieren

Wenn ein Mitarbeiter die Firma verlässt, deaktivierst du sein Konto in Authentik (nicht löschen — das würde Audit-Logs verbuchen, die Referenz auf das alte Konto verlieren). Deaktivierung unter Benutzer → Ausgewählter Benutzer → Konto deaktivieren.

Die Auswirkung: Der Benutzer kann sich nicht mehr anmelden. OIDC-Client-Tokens, die bereits ausgestellt wurden und noch nicht abgelaufen sind — die sind durch das Deaktivieren nicht sofort ungültig. In der Praxis setzt du da zwei Dinge: 1) Deaktivierung des Kontos (sofortiger Login-Ausschluss), 2) evtl. manuelle Revokation der Tokens (wenn das Risiko besteht, dass der Ex-Mitarbeiter Zugang zu einem Dienst hat, der noch ein gültiges Token hält).

Authentik hat eine API, über die sich das automatisieren lässt — in Verbindung mit einem HR-System oder manuell per Skript. In kleinen Firmen mit fünf bis 50 Mitarbeitern reicht es oft, das manuell zu tun und es als Teil des offboarding Prozesses zu dokumentieren.

Vergleich mit interner PKI und Caddy — wo die Dsseuden liegen

BsN hat bereits zwei verwandte Artikel veröffentlicht, die konzeptionell verwandt sind, aber andere Schichten abdecken:

| Schicht | BSI/Interne PKI-Artikel | Caddy-Artikel | SSO-Artikel (dieser) |

|---|---|---|---|

| Was | Zertifikate und Verschlüsselung für Dienste im eigenen Netz — Root-CA, Intermediate, Zertifikatsvergabe an Dienste | Reverse Proxy mit automatischen Zertifikaten oder manueller Terminierung; TLS-Konfiguration für mehrere Dienste | Authentifizierungsschicht: wer darf auf einen Dienst zugreifen, zentral entschieden über OIDC/SAML |

| Abhängigkeit | PKI ist unabhängig von SSO — ein Dienst kann ein eigenes Zertifikat haben, ohne SSO. SSO benötigt aber TLS, sonst sind Tokens in der Luft. | Caddy kann TLS für den Authentik-IdP und den Beispiel-Service gleichzeitig übernehmen. Caddy ist nicht zwingend für SSO nötig, aber praktisch, weil TLS ohne Caddy manuell pro Dienst konfiguriert werden muss. | SSO setzt TLS voraus (sonst sind Authorization-Code und Tokens im Klartext im Netz). SSO setzt PKI nicht voraus (Zertifikate kann man von einer privaten CA oder einer öffentlichen CA beziehen). |

| Typische Fehler | Zertifikatsablauf, Zertifikate für den falschen Hostnamen, Root-CA nicht vertraut auf Client-Geräten | Caddy nicht konfiguriert für mehrere Domains, Zertifikat-Provisionierung fehlerhaft bei internen Domains | SSO konfiguriert, aber Dienste erreichbar ohne SSO (kein Schutz, weil Reverse Proxy nicht konfiguriert ist, SSO zu erzwingen) |

Eine typische Architektur für eine kleine Firma sieht so aus: Interne PKI (via BsN-Artikel) vergibt Zertifikate für alle Dienste, Caddy (via BsN-Artikel) terminiert TLS und routet Traffic, und Authentik (dieser Artikel) sitzt vor oder hinter dem Caddy und authentifiziert Nutzer auf Diensten, die OIDC unterstützen. Dienste, die kein OIDC/SAML unterstützen, bleiben ohne SSO schutz — das ist eine Einschränkung, die du akzeptieren musst, oder du setzt eine alternative Schutzschicht.

Häufige Fehler — drei realistische Beispiele

Fehler 1: Redirect URI stimmt nicht exakt überein

Symptom: Nach dem Login auf Authentik kommt eine Fehlermeldung: „Invalid redirect URI" oder der Nutzer wird abgelehnt, ohne dass ein Token ausgestellt wird.

Ursache: Der Redirect URI, den der Client im Authorization-Request sendet, stimmt nicht exakt mit dem Redirect URI überein, den du in der Authentik-OIDC-Client-Konfiguration eingetragen hast. Ein häufiger Fall: https://python-service.brillianze.de/callback in Authentik, aber im Client steht http:// statt https://, oder ein abschließender Schrägstrich ist vorhanden (/callback/ statt /callback).

Lösung: Redirect URIs in OAuth2 sind case-sensitiv und müssen exakt übereinstimmen. Prüfe in Authentik, was konfiguriert ist, und prüfe im Client-Code, was gesendet wird. curl hilft beim Debuggen — wenn du den Authorization-Request per curl -v sendest, siehst du die exakte URL im Location-Header des Redirects.

Fehler 2: Token-Empfang ohne State-Validierung

Symptom: Der Beispiel-Service funktioniert im Test, aber nach einem Tag merkst du, dass du den state-Parameter nicht validierst.

Ursache: Der state-Parameter im OAuth2-Authorization-Code-Flow dient dem CSRF-Schutz. Ohne Validierung kann ein Angreifer (der den Browser des Nutzers kontrolliert) einen eigenen Authorization-Code einbringen und so die Session des Opfers mit dem eigenen Account verknüpfen. Das ist eine bekannte Attacke (CSRF auf OAuth Authorize-Endpunkten).

Lösung: Der state-Parameter muss im session-Objekt gespeichert werden, bevor der Nutzer zum Authorize-Endpunkt geschickt wird, und nach dem Callback gegen den erhaltenen state validiert werden. authlib macht das in der Standard-Konfiguration automatisch — aber nur, wenn du den state-Parameter nicht selbst überschreibst oder die Session nicht deaktivierst. Prüfe in der authlib-Dokumentation für Flask, ob die Default-State-Handhabung aktiv ist. Für den Beispiel-Service ohne State-Validierung ist das ein Sicherheitsloch — in Produktion beheben.

Fehler 3: SQL-Error nach einem Docker-Compose neustart ohne Datenbank-Persistenz

Symptom: Nach einem Reboot des Servers startet Authentik, meldet aber einen Datenbank-Fehler und die Weboberfläche ist nicht erreichbar.

Ursache: Das docker-compose.yml definiert ein Volume für die PostgreSQL-Daten (./data/postgres:/var/lib/postgresql/data), aber wenn der Pfad falsch gesetzt ist, oder die Docker-Volume-Namenskonvention nicht passt (z.B. bei einem Migrieren auf eine andere Distribution), entsteht eine neue, leere Datenbank bei jedem Start. Die Resultat: Authentik initialisiert sich neu, alle Clients und Benutzer sind weg.

Lösung: Prüfe vor einem Produktiv-Einsatz, ob die Daten nach einem docker compose down && docker compose up -d noch da sind:


docker compose down
docker compose up -d
# Danach: Login mit Admin-Benutzer — funktioniert er noch?

Wenn nein, liegt das Volume falsch. Prüfe docker volume ls und docker inspect <volume-name> auf den Mount-Point.

FAQ

Brauche ich eine GPU für Authentik oder Keycloak?

Nein. Weder Authentik noch Keycloak benötigen GPU-Berechnung. Sie sind Webanwendungen (Python bzw. Java), die ihre Arbeit im CPU-Modus machen. Etwas RAM (2–4 GB) und CPU-Kerne (2+) sind ausreichend; eine GPU bringt keinen Vorteil.

Kann ich Authentik ohne Docker betreiben?

Authentik bringt ein Python-Paket (pip install authentik) und läuft theoretisch ohne Docker. In der Praxis empfiehlt das Authentik-Team Docker oder den Omnibus-Paket (für Debian/Ubuntu, kein Docker nötig). Für einen produktiven Betrieb ohne Docker müsstest du PostgreSQL, Redis, das Authentik-Paket und die Authentik-Webrahmen manuell installieren, konfigurieren und als Systemdienste laufen lassen — das ist mehr Wartung auf dich, als der Docker-Weg. Für ein kleines Homelab oder eine kleine Firma ist Docker die dokumentierte und unterstützte Variante.

Ist Keycloak besser als Authentik?

"Better" hängt vom Anwendungsfall ab. Keycloak hat mehr Features (Identity-Brokering zu anderen IdPs, komplexere Rollen-Engine, SAML-Identity-Provider, User-Federation zu LDAP/AD) — aber mehr Komplexität. Authentik ist simpler, OIDC-first, weniger Konfigurationsaufwand für Standard-Use-Cases. Wenn du eine SAML-lastige Umgebung mit vielen Legacy-Applikationen hast, die nah an AD anbinden wollen, ist Keycloak die sinnvollere Wahl. Wenn du primär moderne Webanwendungen mit OIDC hast, ist Authentik der günstigere Einstieg.

Was ist der Unterschied zwischen OIDC und SAML? Ich dachte, SSO heißt SSO.

OIDC (OpenID Connect) ist ein moderner Standard auf dem HTTPS-Protokoll, JSON-basiert, mit JWT-Tokens — er ist das Protokoll der Wahl für neue Webanwendungen. SAML (Security Assertion Markup Language) ist älter, XML-basiert, schwerer zu handhaben, aber noch in vielen Enterprise- und Legacy-Umgebungen verbreitet (insbesondere bei SOAP-basierten Applikationen und bestimmten Single-Sign-On-Lösungen von Herstellern). Beide lösen SSO — aber OIDC ist leichter zu integrieren in moderne Tech-Stacks, SAML ist in bestimmten Legacy-Kontexten das, was man braucht.

Was passiert, wenn Authentik kompromittiert wird?

Wenn ein Angreifer Zugang zum Authentik-Server und zu seinen Secrets (insbesondere dem AUTHENTIK_SECRET_KEY und den OIDC-Client-Secrets) erhält, kann er Tokens für sich selbst ausstellen, die von verbundenen Diensten als gültig angesehen werden. Das ist der schlechteste Fall. Gegenmaßnahmen: Authentik auf einem dedizierten Server oder mindestens isoliert von anderen Diensten betreiben, Zugriff auf den Server streng begrenzen (nur SSH-Key, kein Passwort-Login, Firewall, Security-Updates), Backups verschlüsselt und extern halten, Secrets nicht in unverschlüsselten Konfigurationsdateien ablegen. Für besonders kritische Umgebungen ist eine ausgelagerte IdP- Variante (Cloudflare Access, ein anderer Managed IdP) eine Alternative — aber mit den Datenabhängigkeiten, die das mit sich bringt.

Interne Verlinkungsvorschläge

1. sicherer-fernzugriff.md — „Sicher von unterwegs auf den Heimserver zugreifen: Tailscale, WireGuard und Reverse-Tunnel im Vergleich" — Verlinkung im Abschnitt zur Architektur: Der SSO-IdP ist meist über den eigenen Reverse Proxy von außen erreichbar; wie du den Tunnel schützt und die Zugänglichkeit regelst, steht in diesem Artikel.

2. docker-vs-vm.md — „Docker-Container vs. VM: Wann welcher Virtualisierungstyp das Richtige ist" — Verlinkung in der Docker-Compose-Anleitung: Wenn dein Server ein Hypervisor ist, kannst du Authentik auch als VM statt als Container betreiben; die Entscheidungshilfe in diesem Artikel hilft dir, die Infrastruktur-Entscheidung zu treffen.

3. Artikel zur internen PKI und Caddy (falls verfasst — andernfalls interner Platzhalter) — Verlinkung im TLS-Abschnitt: Wie du Zertifikate für den Authentik-IdP und den Beispiel-Service beschaffst und einsetzt, steht im PKI-Artikel; die Caddy-Konfiguration für Reverse Proxy steht im Caddy-Artikel.

4. Artikel über Passwort-Management und 2FA in der Firma (falls existent — andernfalls Verweis auf BsN-Praxis) — Verlinkung im Passwortrichtlinien-Abschnitt: Wie du YubiKey/FIDO2 in Authentik einbindest, User-Registration für MFA und Betrieb von Hardware-Schlüsseln in kleinen Teams — sobald dieser Artikel existiert.

Bildvorschläge

**Bild 1: SSO-aal"

Bild 2: Vergleichstabelle der drei Kandidaten

Bild 3: Docker-Compose-Architektur-Skizze

Zielgruppe: Wann ein externer Anbieter wie Cloudflare Access die pragmatischere Wahl ist

Dieser Artikel hat Authentik und Keycloak als selbst-gehostete IdP-Lösungen behandelt. Für viele kleine Firmen (5–50 Mitarbeiter) und fortgeschrittene Homelab-Betreiber ist das der richtige Weg: volle Kontrolle über die Identitätsdaten, keine Abhängigkeit von einem Cloud-Anbieter, keine laufenden Kosten pro Nutzer.

Es gibt Situationen, in denen ein externer Anbieter wie Cloudflare Access die pragmatischere Wahl ist:

Die Entscheidung ist keine Frage von "right vs. wrong", sondern von den tatsächlichen Bedingungen: Was hast du bereits (Server, Domain, Wartung), was willst du vermeiden (Abhängigkeit, Kosten, Komplexität), und was sind die tatsächlichen Integrationsanforderungen deiner Dienste. Für den typischen BsN-Kunden mit fünf bis 15 Mitarbeitern, einem oder zwei Servern und Web-Diensten, die OIDC unterstützen, ist Authentik mit Docker der beileibe pragmatischste Weg — für Kunden mit mehr Legacy-Anforderungen oder ohne Server-Wunschlust ist Cloudflare Access eine legitime, pragmatische Alternative.


Der Artikel liegt als Markdown-Draft vor (C:\00hermes\unternehmen\artikel-neu\drafts\sso-selbst-gehostet.md) und wurde nicht veröffentlicht. Der Publish-Schritt — HTML-Generierung über das BsN-Build-Skript und Push ins getwo/homelab-vergleich-Repository — ist manuell durch Benjamin durchzuführen, nachdem der Artikel auf inhaltliche Korrektheit geprüft wurde.

Hinweis: Dieser Artikel ist redaktioneller Inhalt und enthält keine Affiliate- oder Partner-Links. Alle Angaben entsprechen dem Stand 2026 und können sich ändern.