Pocket-ID: Ein OIDC-Provider, der nur eine Sache kann — und die gut

Warum ich meinen schwergewichtigen Authentik-Stack gegen das schlanke, passkey-zentrierte Pocket-ID als zentralen OIDC-Provider getauscht habe
Table of contents

Single Sign-On im Homelab ist ein zweischneidiges Schwert: Man will einen Login für alles, handelt sich damit aber schnell einen Identity-Provider ein, der schwerer wiegt als die Dienste, die er absichert. Genau an diesem Punkt habe ich meinen bisherigen Authentik -Stack gegen Pocket-ID getauscht — erreichbar unter auth.this-is-fine.io und seitdem der zentrale OIDC-Provider für mein gesamtes Lab.

Warum Authentik weichen musste#

Authentik ist exzellente Software und ein vollwertiger Identity-Provider: SAML, OAuth2/OIDC, LDAP, ausgefeilte Authentication-Flows, Policy-Engine, Outposts für Proxy-Auth. Genau diese Mächtigkeit hat aber ihren Preis. Im Cluster bedeutete Authentik einen server, einen worker, eine PostgreSQL-Datenbank und einen Redis-kompatiblen Cache — ein kleiner verteilter Stack, nur damit ich mich irgendwo einloggen kann.

Für eine Organisation mit hunderten Nutzern, SAML-Zwang und komplexen Föderationsanforderungen ist das angemessen. Für mein Lab, in dem genau ein Mensch und eine Handvoll Dienste OIDC sprechen, war es schlicht überdimensioniert. Die Erkenntnis: Ich nutzte vielleicht fünf Prozent der Features und betrieb hundert Prozent der Komplexität.

Das ist ausdrücklich kein Argument gegen Authentik — wer SAML, LDAP-Outposts oder feingranulare Flow-Policies wirklich braucht, ist dort goldrichtig aufgehoben. Es ist ein Argument für die richtige Werkzeuggröße: Man sollte die Komplexität bezahlen, die man auch verbraucht.

Was Pocket-ID anders macht#

Pocket-ID dreht die Prioritäten um: Es ist ein reiner OIDC-Provider, bewusst auf das Nötigste reduziert. Kein SAML, kein LDAP, keine Flow-Engine — dafür ein einzelner Prozess, der seinen Zustand in einer SQLite-Datei hält. Kein externer Datenbank-Cluster, kein Cache-Deployment, keine Outposts.

Der zweite, eigentlich charmante Aspekt ist die Authentifizierung selbst: Pocket-ID ist von Grund auf passkey-zentriert und setzt auf WebAuthn /FIDO2. Statt Passwörter zu verwalten, melde ich mich mit einem Hardware- oder Plattform-Authenticator an — phishing-resistent und ohne Passwort-Datenbank, die geleakt werden könnte.

Im Cluster ist das Deployment entsprechend unspektakulär: ein Container, eine APP_URL, fertig.

1image: ghcr.io/pocket-id/pocket-id:v2.5.0
2env:
3  - name: APP_URL
4    value: "https://auth.${EXTERNAL_DOMAIN}"

Nach außen hängt der Dienst über eine HTTPRoute am Envoy-Gateway unter auth.this-is-fine.io, mit TLS aus cert-manager. Das war der gesamte operative Aufwand.

Ein Provider für alles#

Der eigentliche Gewinn zeigt sich erst im Zusammenspiel: Pocket-ID ist heute die eine Stelle, an der sich Menschen im Lab ausweisen. Jeder Dienst, der OIDC spricht, hängt als Client daran:

flowchart TB PID["Pocket-ID<br/>auth.this-is-fine.io<br/>(SQLite · Passkeys)"] HS["Headscale<br/>(Tailnet-Enrollment)"] ZOT["Zot-Registry<br/>(Web-UI-Login)"] EG["Envoy-Gateway<br/>(OIDC für Apps ohne SSO)"] GRAF["Grafana, …<br/>(via SecurityPolicy)"] HS -->|"OIDC client"| PID ZOT -->|"OIDC client"| PID EG -->|"SecurityPolicy oidc"| PID GRAF --- EG
  • Headscale nutzt Pocket-ID für das Tailnet-Enrollment von Menschen — keine manuellen Pre-Auth-Keys mehr, nur noch OIDC-Login, Maschinen behalten ihre getaggten Keys.
  • Die Zot-Registry bindet Pocket-ID für den Web-UI-Login der Menschen an, während Maschinen weiterhin über htpasswd authentifizieren.
  • Das Envoy-Gateway kann über eine SecurityPolicy sogar Diensten ein OIDC-Login vorschalten, die selbst gar kein SSO können — die Anmeldung passiert dann am Gateway, lange bevor der Request die App erreicht.

Wie ein Dienst andockt#

Spricht eine Anwendung OIDC nativ, braucht sie überraschend wenig: die Issuer-URL, eine client_id, exakt hinterlegte Redirect-URIs und ein Client-Secret. Alles Weitere — Endpunkte, Schlüssel, unterstützte Scopes — holt sie sich selbst über die Discovery unter /.well-known/openid-configuration. Bei der Zot-Registry steht im Kern nur das hier; Pocket-ID ist der Issuer, die Gruppen aus dem Token mappen auf Registry-Rollen:

1"openid": {
2  "providers": {
3    "oidc": {
4      "issuer": "https://auth.this-is-fine.io",
5      "scopes": ["openid", "profile", "email", "groups"],
6      "claimMapping": { "username": "preferred_username" }
7    }
8  }
9}

Der Login selbst ist der klassische Authorization-Code-Flow — die App übernimmt Session und Consent, ohne dass ein Auth-Proxy davor sitzt:

sequenceDiagram participant U as Browser participant Z as Zot · oci.this-is-fine.io participant P as Pocket-ID · auth.this-is-fine.io U->>Z: GET /ui Z-->>U: 302 → /authorize U->>P: Login per Passkey + Consent P-->>U: Redirect mit Authorization Code U->>Z: Code → Token-Tausch Z-->>U: Registry-Session
Redirect-URI zuerst registrieren. Jeder Client gehört in Pocket-ID angelegt — mit exakter Redirect-URL und Scopes — bevor die App auf OIDC umgestellt wird. redirect_uri_mismatch ist der mit Abstand häufigste First-Login-Fehler, und die Meldung kommt aus der IdP-UI, nicht aus den Logs der App.

Fazit#

Der Wechsel von Authentik zu Pocket-ID hat mir nicht ein einziges Feature genommen, das ich tatsächlich genutzt habe — wohl aber einen PostgreSQL-Cluster, einen Cache und einen Worker erspart. Übrig bleibt ein einzelner, passkey-erster Prozess, der genau eine Aufgabe erfüllt und sie aus dem Weg geht. Manchmal ist die beste Architekturentscheidung schlicht, das kleinere Werkzeug zu nehmen.