Deployn

Pocket ID installieren: Single Sign-On mit Passkeys für deine Self-Hosted-Apps

Pocket ID mit Docker und Caddy installieren und Apps wie Paperless-ngx, Actual Budget und Forgejo anbinden. Mit Passkeys, Gruppen und Beispielen.

Veröffentlicht am
Pocket ID installieren: Single Sign-On mit Passkeys für deine Self-Hosted-Apps hero image
Inhalt
Getestet mit
Pocket ID 2.19.2Caddy 2.11Docker 29.9Ubuntu 26.04Actual Budget 26.11.0Paperless-ngx 3.4.2Forgejo 17.0.1nginx 1.30oauth2-proxy 7.15
Zuletzt geprüft am

Wer mehrere selbst gehostete Apps betreibt, kennt das Problem: Jede App hat ihr eigenes Login mit eigenem Passwort. Pocket ID löst das. Du meldest dich einmal bei Pocket ID an, und zwar mit einem Passkey statt mit einem Passwort. Danach loggst du dich bei allen verbundenen Apps mit einem Klick ein.

In diesem Beitrag installiere ich Pocket ID mit Docker und Caddy, lege mein Konto an und verbinde vier Beispiele: Actual Budget, Paperless-ngx, Forgejo und eine normale Webseite, die gar keine eigene Anmeldung kennt.

Was ist Pocket ID?

Pocket ID ist ein OpenID-Connect-Anbieter (OIDC). Das ist ein Standard, mit dem eine App die Anmeldung an einen anderen Dienst auslagert. Die App fragt Pocket ID: “Wer ist das?”, und Pocket ID antwortet mit Benutzername und E-Mail-Adresse.

Das Besondere an Pocket ID: Es kennt keine Passwörter. Du meldest dich ausschließlich mit Passkeys an, zum Beispiel mit Fingerabdruck oder Gesichtserkennung am Handy, einem Passwortmanager oder einem Hardware-Schlüssel. Pocket ID ist deutlich schlanker als Authentik oder Keycloak und braucht nur einen einzigen Container.

Das sind die wichtigsten Begriffe:

Begriff Bedeutung
OIDC OpenID Connect, der Standard für die Anmeldung über einen zentralen Dienst.
Client Eine App, die sich bei Pocket ID anmeldet, zum Beispiel Paperless-ngx.
Passkey Anmeldung ohne Passwort, die auf einem kryptografischen Schlüssel auf deinem Gerät beruht.
Callback-URL Die Adresse, zu der Pocket ID den Benutzer nach der Anmeldung zurückschickt.
Client-ID und Client-Secret Zugangsdaten, mit denen sich die App bei Pocket ID ausweist.
Benutzergruppe Sammlung von Benutzern, mit der du den Zugriff auf Apps steuerst.

Voraussetzungen

  1. Einen Server: Ich nutze einen kleinen VPS bei Hetzner mit Ubuntu 26.04. Pocket ID selbst braucht kaum Ressourcen.
  2. Eine Domain mit Subdomains: Pocket ID bekommt eine eigene Adresse, zum Beispiel {{id_domain}}, und jede App ebenfalls, zum Beispiel paperless.{{base_domain}}. Alle müssen per A-Record auf deinen Server zeigen. Ein Wildcard-Eintrag *.{{base_domain}} spart dir die einzelnen Einträge.
  3. Offene Ports: 80 und 443 müssen erreichbar sein.
  4. Ein Gerät mit Passkey-Unterstützung: Das kann dein Smartphone, dein Passwortmanager oder ein Sicherheitsschlüssel sein. Passkeys funktionieren nur über HTTPS, deshalb brauchst du die Domain.

Docker installieren

Verbinde dich per SSH mit dem Server und installiere Docker:

Terminalbash
curl -fsSL https://get.docker.com | sudo sh

Danach legst du ein gemeinsames Docker-Netzwerk an. Über dieses Netzwerk erreicht Caddy später alle Apps:

Terminalbash
sudo docker network create proxy

Pocket ID mit Docker Compose und Caddy starten

Erstelle ein Verzeichnis für Pocket ID:

Terminalbash
mkdir ~/pocketid
cd ~/pocketid

Lege die compose.yaml an:

Terminalbash
nano compose.yaml
yaml
services:
    pocket-id:
        image: pocketid/pocket-id:v2
        container_name: pocket-id
        restart: unless-stopped
        env_file: .env
        volumes:
            - ./data:/app/data
        healthcheck:
            test: ["CMD", "/app/pocket-id", "healthcheck"]
            interval: 1m30s
            timeout: 5s
            retries: 2
            start_period: 10s
        networks:
            - proxy

    caddy:
        image: caddy:2
        container_name: caddy
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile
            - caddy_data:/data
            - caddy_config:/config
        networks:
            - proxy

volumes:
    caddy_data:
    caddy_config:

networks:
    proxy:
        external: true

Der Ordner ./data enthält die Datenbank von Pocket ID. Der Tag v2 hält dich auf der aktuellen Hauptversion.

Erzeuge einen zufälligen Schlüssel:

Terminalbash
openssl rand -base64 32

Lege die .env-Datei an und trage dort den Schlüssel ein:

Terminalbash
nano .env
Terminalbash
APP_URL=https://{{id_domain}}
ENCRYPTION_KEY=HIER-DEN-ZUFAELLIGEN-SCHLUESSEL-EINTRAGEN
TRUST_PROXY=true
PUID=1000
PGID=1000
  • APP_URL ist die Adresse, unter der du Pocket ID öffnest, ohne Pfad am Ende.
  • ENCRYPTION_KEY verschlüsselt sensible Daten wie die Signaturschlüssel. Sichere ihn, denn ohne ihn lässt sich die Datenbank nicht mehr nutzen.
  • TRUST_PROXY=true sorgt dafür, dass Pocket ID im Audit-Protokoll die echte IP-Adresse der Benutzer sieht und nicht die von Caddy.
  • PUID und PGID legen fest, welcher Benutzer die Dateien in ./data besitzt.

Lege die Caddyfile an:

Terminalbash
nano Caddyfile
{{id_domain}} {
    reverse_proxy pocket-id:1411
}

Starte beide Container:

Terminalbash
sudo docker compose up -d

Mit sudo docker compose ps prüfst du den Status. Pocket ID meldet healthy, sobald es bereit ist. Ob Caddy das Zertifikat bekommen hat, siehst du hier:

Terminalbash
sudo docker compose logs caddy | grep "certificate obtained"

Dein Admin-Konto anlegen

Öffne https://{{id_domain}}/setup im Browser. Diese Seite funktioniert nur, solange es noch kein Konto gibt. Dort legst du Benutzername, E-Mail-Adresse und Namen fest und klickst auf “Sign Up”. Danach fügst du mit “Add Passkey” deinen ersten Passkey hinzu.

Erledige das sofort nach dem Start. Solange kein Konto existiert, kann jeder, der die Adresse kennt, das Admin-Konto anlegen. Auf meinem Testserver gab es schon wenige Minuten nach dem Start Zugriffe von Scannern auf die Domain.

Du landest danach in den Einstellungen. Unter “Mein Konto” siehst du deine Passkeys:

Mein Konto in Pocket ID mit dem hinterlegten Passkey

Pocket ID weist oben darauf hin, dass nur ein Passkey hinterlegt ist. Füge mit “Passkey hinzufügen” einen zweiten hinzu, zum Beispiel auf einem anderen Gerät. Sonst sperrst du dich aus, wenn du das einzige Gerät verlierst. Die Sprache stellst du am Ende derselben Seite um.

Die Oberfläche im Überblick

Links findest du “Mein Konto”, “Meine Apps” und “Audit-Protokoll”. Unter “Verwaltung” liegen die Administrationsseiten für Benutzer, Benutzergruppen und OIDC-Clients.

Unter “Benutzer” siehst du alle Konten. Der Benutzer “Static API User” erscheint nur, wenn du in der .env einen STATIC_API_KEY gesetzt hast, und lässt sich ignorieren:

Benutzerliste in der Verwaltung

Eine App verbinden: So funktioniert es immer

Jede App läuft nach demselben Muster an. Du legst in Pocket ID einen Client an, kopierst drei Werte in die App und gibst die Callback-URL der App in Pocket ID ein.

Gehe zu “Verwaltung” -> “OIDC Clients” und klicke auf “OIDC-Client hinzufügen”. Es erscheint ein Formular:

Formular zum Erstellen eines OIDC-Clients

Du füllst drei Dinge aus:

  • Name: Der Name der App. Er erscheint später beim Anmelden.
  • Clienttyp: “Vertraulicher Client” ist richtig für Apps mit eigenem Server, das sind fast alle in diesem Beitrag. “Öffentlicher Client” ist für reine Browser-, Mobil- oder Desktop-Apps gedacht.
  • Callback-URL: Die Adresse, zu der Pocket ID den Benutzer zurückschickt. Pocket ID leitet nur an URLs weiter, die du hier einträgst. Die richtige URL steht in der Dokumentation der jeweiligen App, unten findest du sie für alle Beispiele.

Nach “Erzeugen” öffnet sich die Seite des Clients. Dort stehen alle Werte, die du für die App brauchst:

Details eines OIDC-Clients mit Client-ID und Discovery-URL

Über “Mehr Details anzeigen” siehst du zusätzlich die einzelnen Adressen:

Endpunkte eines OIDC-Clients

Die meisten Apps brauchen nur die Client-ID, das Client-Secret und die Discovery-URL https://{{id_domain}}/.well-known/openid-configuration. Manche Apps wollen die Adressen einzeln haben:

Wert Adresse
Aussteller (Issuer) https://{{id_domain}}
Autorisierung https://{{id_domain}}/authorize
Token https://{{id_domain}}/api/oidc/token
Benutzerinfo https://{{id_domain}}/api/oidc/userinfo
Abmelden https://{{id_domain}}/api/oidc/end-session

Das Client-Secret zeigt Pocket ID nur ein einziges Mal direkt nach dem Erstellen. Kopiere es sofort. Unter dem Reiter “Zugangsdaten” legst du bei Bedarf ein neues Secret an, und mehrere Secrets können gleichzeitig aktiv sein:

Reiter Zugangsdaten mit Client-Secrets

Als Scopes nutzen die meisten Apps openid profile email.

Beispiel 1: Actual Budget

Actual Budget hat OIDC eingebaut. Die Callback-URL lautet https://actual.{{base_domain}}/openid/callback. Lege in Pocket ID einen Client mit diesem Namen und dieser URL an und kopiere Client-ID und Secret.

Wie du Actual Budget selbst installierst, steht in meiner Anleitung zu Actual Budget. Für Pocket ID legst du neben der compose.yaml eine .env an:

Terminalbash
ACTUAL_OPENID_DISCOVERY_URL=https://{{id_domain}}
ACTUAL_OPENID_CLIENT_ID=DEINE-CLIENT-ID
ACTUAL_OPENID_CLIENT_SECRET=DEIN-CLIENT-SECRET
ACTUAL_OPENID_SERVER_HOSTNAME=https://actual.{{base_domain}}

ACTUAL_OPENID_SERVER_HOSTNAME ist die Adresse von Actual selbst. Ohne diese Zeile bietet Actual die OpenID-Anmeldung nicht an. In der Docker-Compose-Datei von Actual muss der Service die Datei einlesen:

yaml
services:
    actual_server:
        image: actualbudget/actual-server:latest
        container_name: actual_server
        restart: unless-stopped
        env_file: .env
        volumes:
            - ./data:/data
        networks:
            - proxy

networks:
    proxy:
        external: true

Wichtig ist env_file: Docker Compose liest eine .env-Datei nur für Platzhalter in der Compose-Datei, nicht automatisch in den Container. Starte Actual neu mit sudo docker compose up -d --force-recreate.

Ergänze in der Caddyfile von Pocket ID einen Eintrag für die App und lade Caddy neu:

actual.{{base_domain}} {
    reverse_proxy actual_server:5006
}
Terminalbash
cd ~/pocketid
sudo docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

Dieses Muster gilt für alle Beispiele. Ein neuer Eintrag in der Caddyfile, ein Reload, fertig. Der Name hinter reverse_proxy ist der container_name der App, und die App muss im Netzwerk proxy hängen.

Wenn du https://actual.{{base_domain}} öffnest, siehst du jetzt “Bei dieser Actual-Instanz anmelden” mit dem Button “OpenID verwenden”:

Actual Budget mit dem Button OpenID verwenden

Der erste Benutzer, der sich über OpenID anmeldet, wird Servereigentümer. Das lässt sich nicht über die Oberfläche ändern. Melde dich also zuerst selbst an. Nach dem Klick fragt Pocket ID, ob Actual auf deine E-Mail-Adresse und dein Profil zugreifen darf. Du bestätigst mit “Anmelden”. Oben rechts in Actual steht danach “angemeldet als” mit deinem Benutzernamen:

Actual Budget mit Anmeldung über Pocket ID

Beispiel 2: Paperless-ngx

Für Paperless-ngx lautet die Callback-URL:

https://paperless.{{base_domain}}/accounts/oidc/pocket-id/login/callback/

Das Stück pocket-id in der Mitte ist die provider_id aus der Konfiguration unten. Beides muss übereinstimmen. Die Installation von Paperless-ngx beschreibe ich in meiner Paperless-Anleitung für den VPS. Auf dem Webserver-Container setzt du zwei Umgebungsvariablen. Ich lege sie in eine .env-Datei, die der Service per env_file einliest:

Terminalbash
PAPERLESS_URL=https://paperless.{{base_domain}}
PAPERLESS_APPS=allauth.socialaccount.providers.openid_connect
PAPERLESS_SOCIALACCOUNT_PROVIDERS={"openid_connect":{"SCOPE":["openid","profile","email"],"OAUTH_PKCE_ENABLED":true,"APPS":[{"provider_id":"pocket-id","name":"Pocket ID","client_id":"DEINE-CLIENT-ID","secret":"DEIN-CLIENT-SECRET","settings":{"server_url":"https://{{id_domain}}"}}]}}

PAPERLESS_SOCIALACCOUNT_PROVIDERS muss in einer einzigen Zeile stehen. Ohne Anführungszeichen drumherum, sonst liest Compose sie mit.

Nach einem Neustart zeigt die Anmeldeseite von Paperless unter dem Passwortformular den Eintrag “oder anmelden über” und einen Button “Pocket ID”. Beim ersten Login fragt Paperless noch einmal nach Benutzername und E-Mail-Adresse, vorausgefüllt aus Pocket ID:

Paperless-ngx fragt beim ersten Login nach der Registrierung

Mit der Variable PAPERLESS_SOCIAL_AUTO_SIGNUP=true überspringst du diese Seite, und Paperless legt den Benutzer sofort an.

Wichtig: Ein neuer Benutzer über Pocket ID hat in Paperless zunächst keine Rechte. Die Oberfläche lädt dann nicht richtig, und im Log stehen Zeilen wie Forbidden: /api/ui_settings/. Du hast zwei Wege:

  • Einmalig: Ein bestehender Admin gibt dem Benutzer unter “Einstellungen” -> “Benutzer & Gruppen” Rechte. Hast du noch keinen Admin, geht es auf dem Server:
Terminalbash
sudo docker compose exec webserver python3 manage.py shell -c "
from django.contrib.auth.models import User
u = User.objects.get(username='dein-benutzername')
u.is_superuser = True
u.is_staff = True
u.save()
"
  • Dauerhaft: Mit PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS weist du neuen SSO-Benutzern automatisch eine Paperless-Gruppe zu, deren Rechte du vorher in Paperless angelegt hast.

Danach öffnet sich das Dashboard:

Paperless-ngx Dashboard nach der Anmeldung über Pocket ID

Zwei weitere Variablen sind praktisch: PAPERLESS_REDIRECT_LOGIN_TO_SSO=true leitet die Anmeldeseite direkt zu Pocket ID weiter. Mit PAPERLESS_DISABLE_REGULAR_LOGIN=true verschwindet das Passwortformular. Aktiviere Letzteres erst, wenn der SSO-Login sicher funktioniert und mindestens ein Konto Adminrechte hat.

Beispiel 3: Forgejo

Für Forgejo lautet die Callback-URL:

https://git.{{base_domain}}/user/oauth2/PocketID/callback

Forgejo baut die Callback-URL aus dem Namen der Anmeldequelle. Der Teil PocketID in der URL muss deshalb genau dem Namen entsprechen, den du weiter unten bei “Authentifizierungsname” einträgst, auch in der Groß- und Kleinschreibung. Wählst du einen anderen Namen, ersetzt du PocketID in der URL durch diesen Namen. Wie du Forgejo installierst, steht in meiner Forgejo-Anleitung.

Es gibt zwei Wege. Über die Oberfläche gehst du in Forgejo zu “Administratoreinstellungen” -> “Identität u. Zugriff” -> “Authentifizierungsquellen” und klickst auf “Authentifizierungsquelle hinzufügen”. Dort wählst du:

  • Authentifizierungstyp: OAuth2
  • Authentifizierungsname: PocketID
  • OAuth2-Anbieter: OpenID Connect
  • Client-ID und Client-Geheimnis: die Werte aus Pocket ID
  • OpenID-Connect-Auto-Discovery-URL: https://{{id_domain}}/.well-known/openid-configuration
  • Zusätzliche Bereiche: openid email profile
  • Lokale 2FA überspringen: angehakt, denn Pocket ID übernimmt die Absicherung

Schneller geht es auf dem Server mit einem einzigen Befehl:

Terminalbash
sudo docker exec -u git forgejo forgejo admin auth add-oauth \
  --name PocketID \
  --provider openidConnect \
  --key "DEINE-CLIENT-ID" \
  --secret "DEIN-CLIENT-SECRET" \
  --auto-discover-url https://{{id_domain}}/.well-known/openid-configuration \
  --scopes "openid email profile" \
  --skip-local-2fa

Mit sudo docker exec -u git forgejo forgejo admin auth list prüfst du das Ergebnis. In der Oberfläche steht die Quelle dann in der Liste:

Authentifizierungsquellen in Forgejo mit PocketID

Auf der Anmeldeseite von Forgejo erscheint der Button “Anmelden mit PocketID”:

Forgejo-Anmeldeseite mit dem Button Anmelden mit PocketID

Beim ersten Login fragt Forgejo, ob du ein neues Konto anlegen oder ein bestehendes verknüpfen möchtest. Danach bist du angemeldet:

Forgejo nach der Anmeldung über Pocket ID

Beispiel 4: Eine Seite ohne eigenes Login schützen

Nicht jede App kann OIDC. Eine statische Webseite, ein Dashboard oder ein Tool ohne Login lassen sich trotzdem hinter Pocket ID setzen. Dafür brauchst du oauth2-proxy. Das ist ein kleiner Dienst, der die Anmeldung bei Pocket ID übernimmt. Dein Reverse Proxy fragt ihn bei jedem Aufruf, ob der Besucher angemeldet ist.

Der Ablauf: Ein Besucher ruft https://site.{{base_domain}} auf. Der Reverse Proxy fragt oauth2-proxy. Ist der Besucher nicht angemeldet, schickt er ihn zu Pocket ID. Nach der Anmeldung liefert er die Seite aus. Die App dahinter braucht von alldem nichts zu wissen.

Das geht mit Caddy, den du ohnehin für HTTPS nutzt, und mit nginx. Ich zeige beide Varianten. Nimm eine davon, denn beide brauchen dieselbe Adresse site.{{base_domain}}.

Lege in Pocket ID einen Client an, zum Beispiel mit dem Namen “Caddy Seite” und der Callback-URL:

https://site.{{base_domain}}/oauth2/callback

Beide Varianten brauchen dieselbe .env für oauth2-proxy. Lege sie im Ordner der Variante an, die du nutzt. Das Cookie-Secret erzeugst du mit openssl rand -base64 32 | tr '+/' '-_':

Terminalbash
OAUTH2_PROXY_PROVIDER=oidc
OAUTH2_PROXY_OIDC_ISSUER_URL=https://{{id_domain}}
OAUTH2_PROXY_CLIENT_ID=DEINE-CLIENT-ID
OAUTH2_PROXY_CLIENT_SECRET=DEIN-CLIENT-SECRET
OAUTH2_PROXY_COOKIE_SECRET=DEIN-COOKIE-SECRET
OAUTH2_PROXY_REDIRECT_URL=https://site.{{base_domain}}/oauth2/callback
OAUTH2_PROXY_EMAIL_DOMAINS=*
OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
OAUTH2_PROXY_COOKIE_SECURE=true
OAUTH2_PROXY_REVERSE_PROXY=true
OAUTH2_PROXY_SET_XAUTHREQUEST=true
OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true

Ein paar Zeilen erkläre ich:

  • OAUTH2_PROXY_EMAIL_DOMAINS=* erlaubt jede E-Mail-Domain. Wer sich anmelden darf, regelst du in Pocket ID über Gruppen.
  • OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true brauchst du, weil Pocket ID E-Mail-Adressen standardmäßig nicht als verifiziert markiert, oauth2-proxy verifizierte Adressen aber standardmäßig erwartet.
  • OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true schickt Besucher direkt zu Pocket ID, statt erst eine Seite mit einem Button anzuzeigen.
  • OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256 schaltet PKCE ein, einen zusätzlichen Schutz gegen abgefangene Anmeldecodes.

Variante A: Caddy mit forward_auth

Caddy hat dafür die Anweisung forward_auth. Du brauchst keinen zusätzlichen Webserver und keine zusätzliche Konfigurationsdatei: Eine App schützt du mit einem Block in der Caddyfile.

Lege einen Ordner für oauth2-proxy an und speichere die .env von oben darin:

Terminalbash
mkdir ~/oauth2-proxy
cd ~/oauth2-proxy
nano .env

Die compose.yaml enthält nur oauth2-proxy:

yaml
services:
    oauth2-proxy:
        image: quay.io/oauth2-proxy/oauth2-proxy:latest
        container_name: oauth2-proxy
        restart: unless-stopped
        env_file: .env
        networks:
            - proxy

networks:
    proxy:
        external: true

Starte es mit sudo docker compose up -d. Dann ersetzt du in der Caddyfile von Pocket ID den Eintrag für site.{{base_domain}} durch diesen Block:

site.{{base_domain}} {
    route {
        reverse_proxy /oauth2/* oauth2-proxy:4180 {
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-Uri {uri}
        }

        forward_auth oauth2-proxy:4180 {
            uri /oauth2/auth
            header_up X-Real-IP {remote_host}
            copy_headers X-Auth-Request-User X-Auth-Request-Email X-Auth-Request-Preferred-Username
            @error status 401
            handle_response @error {
                redir * /oauth2/sign_in?rd={scheme}://{host}{uri}
            }
        }

        reverse_proxy DEINE-APP:80
    }
}

So funktioniert der Block:

  • route legt fest, dass Caddy die Anweisungen in dieser Reihenfolge abarbeitet. Ohne route sortiert Caddy sie selbst, und die Prüfung kann nach der App laufen.
  • Die erste Zeile gibt alle Adressen unter /oauth2/ an oauth2-proxy weiter, zum Beispiel die Anmeldeseite und den Callback.
  • forward_auth fragt oauth2-proxy bei jedem Aufruf, ob der Besucher angemeldet ist. Bei einem 401 leitet die redir-Zeile zur Anmeldung weiter. Sonst kopiert copy_headers Benutzername und E-Mail-Adresse als Header an die App.
  • reverse_proxy DEINE-APP:80 ist deine eigentliche App. Der Name ist der container_name, und der Container hängt im Netzwerk proxy.

Lade Caddy neu:

Terminalbash
cd ~/pocketid
sudo docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile

Ich habe den Block mit einer Testseite geprüft. Statt reverse_proxy stand dort diese Zeile, die den Header aus der Anmeldung ausgibt:

respond "Angemeldet als {http.request.header.X-Auth-Request-Preferred-Username} ({http.request.header.X-Auth-Request-Email})"

Wenn du https://site.{{base_domain}} öffnest und bei Pocket ID noch nicht angemeldet bist, siehst du den Anmeldebildschirm. Dort klickst du auf “Anmelden” und bestätigst mit deinem Passkey. Bist du schon angemeldet, fragt Pocket ID nur noch nach der Freigabe:

Pocket ID fragt, ob du dich bei der Caddy-Seite anmelden möchtest

Danach liefert Caddy die Seite aus:

Die Seite zeigt Benutzername und E-Mail-Adresse aus der Pocket-ID-Anmeldung

Deine App bekommt die Header X-Auth-Request-Preferred-Username und X-Auth-Request-Email bei jedem Aufruf mit. Apps, die einen vorgelagerten Login unterstützen, können damit Benutzer automatisch anlegen oder erkennen. Das heißt “Header Authentication” und steht in der Doku der jeweiligen App.

Beim Abmelden gibt es einen Stolperstein. Der Link /oauth2/sign_out beendet nur die Sitzung bei oauth2-proxy. Läuft die Sitzung bei Pocket ID noch, leitet Pocket ID dich beim nächsten Aufruf ohne Rückfrage wieder zurück, und du bist sofort wieder angemeldet. Willst du dich ganz abmelden, meldest du dich zusätzlich in Pocket ID ab. Dort klickst du oben rechts auf deinen Avatar und dann auf “Abmelden”.

Variante B: nginx mit auth_request

Läuft deine Seite schon hinter nginx, setzt du die Prüfung dort ein. Hier ist es eine statische Seite. Alles passiert in einem Ordner:

Erstelle dann ein Verzeichnis mit drei Dateien:

Terminalbash
mkdir -p ~/nginx-site/html
cd ~/nginx-site

Die Datei html/index.html ist deine Seite. Für das Beispiel reicht eine einfache:

html
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Geschützte Seite</title></head>
<body>
<h1>Geschützte Seite</h1>
<p>Diese Seite liefert nginx aus. Ohne Anmeldung bei Pocket ID siehst du sie nicht.</p>
<p><a href="/whoami">Wer bin ich?</a> · <a href="/oauth2/sign_out">Abmelden</a></p>
</body>
</html>

Die Datei html/whoami.html zeigt nach dem Login, wer angemeldet ist. nginx füllt die Platzhalter per SSI (Server Side Includes):

html
<p>Angemeldet als <!--# echo var="user" --> (<!--# echo var="email" -->)</p>

Die Datei nginx.conf ist der Kern:

nginx
server {
    listen 80;
    server_name site.{{base_domain}};

    location /oauth2/ {
        proxy_pass http://oauth2-proxy:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Auth-Request-Redirect $request_uri;
    }

    location = /oauth2/auth {
        internal;
        proxy_pass http://oauth2-proxy:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Content-Length "";
        proxy_pass_request_body off;
    }

    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        auth_request_set $user  $upstream_http_x_auth_request_preferred_username;
        auth_request_set $email $upstream_http_x_auth_request_email;

        root /usr/share/nginx/html;
        index index.html;
    }

    location = /whoami {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        auth_request_set $user  $upstream_http_x_auth_request_preferred_username;
        auth_request_set $email $upstream_http_x_auth_request_email;

        ssi on;
        default_type text/html;
        alias /usr/share/nginx/html/whoami.html;
    }
}

Die Zeile auth_request /oauth2/auth; ist der Türsteher. Sie steht in jedem location-Block, den du schützen willst. Alles, was in location / steht, ist nur durch den Login erreichbar.

Die .env von oben legst du in denselben Ordner. Die compose.yaml enthält nginx und oauth2-proxy:

yaml
services:
    nginx:
        image: nginx:stable
        container_name: nginx_site
        restart: unless-stopped
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
            - ./html:/usr/share/nginx/html:ro
        depends_on:
            - oauth2-proxy
        networks:
            - proxy

    oauth2-proxy:
        image: quay.io/oauth2-proxy/oauth2-proxy:latest
        container_name: oauth2-proxy
        restart: unless-stopped
        env_file: .env
        networks:
            - proxy

networks:
    proxy:
        external: true

Auch hier ein Eintrag in der Caddyfile von Pocket ID und ein Reload. Der alte Block für site.{{base_domain}} aus Variante A entfällt:

site.{{base_domain}} {
    reverse_proxy nginx_site:80
}

Starte alles mit sudo docker compose up -d. Beim ersten Aufruf von https://site.{{base_domain}} leitet dich nginx zu Pocket ID weiter, das fragt die Freigabe ab:

Pocket ID fragt, ob die nginx-Seite auf E-Mail und Profil zugreifen darf

Nach der Bestätigung siehst du deine Seite:

Die geschützte nginx-Seite nach dem Login

Unter /whoami zeigt nginx, wer angemeldet ist. Die Werte kommen aus den Headern, die oauth2-proxy mitliefert. Du kannst sie in jeder App hinter nginx weiterreichen, zum Beispiel mit proxy_set_header X-User $user;:

Die Seite whoami zeigt Benutzername und E-Mail-Adresse

Wenn du nginx-Konfigurationsdateien änderst, die du als einzelne Datei in den Container mountest, lade nicht nur neu. Viele Editoren und sed -i ersetzen die Datei durch eine neue, und der Container sieht dann weiter die alte. Starte den Container nach solchen Änderungen mit sudo docker compose up -d --force-recreate nginx neu.

Weitere Apps

Für über 100 Apps gibt es fertige Anleitungen in der Pocket-ID-Dokumentation, darunter Immich, Karakeep, Nextcloud, Vaultwarden, Proxmox, Jellyfin, Open WebUI, Gitea, Grafana und Portainer. Das Muster ist immer gleich: Client anlegen, Callback-URL eintragen, Client-ID, Secret und Discovery-URL in die App kopieren. Wie Karakeep läuft, steht in meiner Karakeep-Anleitung.

Zugriff mit Benutzergruppen einschränken

Standardmäßig darf jeder Benutzer von Pocket ID sich bei jedem Client anmelden. Das änderst du mit Gruppen. Das ist sinnvoll, wenn du Familie, Kollegen oder Freunde auf deinem Server hast, aber nicht jeder jede App benutzen soll.

Gehe zu “Verwaltung” -> “Benutzergruppen” und klicke auf “Gruppe hinzufügen”. Du gibst einen Anzeigenamen und einen Namen ein. Der Name erscheint später im groups-Claim, den Apps wie Grafana oder Paperless für Rollen auswerten können. Pocket ID füllt das Namensfeld automatisch aus dem Anzeigenamen. Prüfe es, bevor du speicherst, sonst steht der Text doppelt darin.

Benutzergruppe erstellen

Im Reiter “Benutzer” der Gruppe wählst du die Mitglieder aus und speicherst:

Benutzer einer Gruppe zuweisen

Danach liegt die Gruppe in der Übersicht:

Liste der Benutzergruppen

Jetzt schränkst du einen Client ein. Öffne ihn unter “OIDC Clients”, wechsle in den Reiter “Zugang” und wähle “Ausgewählte Benutzergruppen”. Dann hakst du die erlaubten Gruppen an und klickst auf “Speichern”:

Zugang eines Clients auf ausgewählte Gruppen beschränken

Benutzer, die in keiner dieser Gruppen sind, sehen bei der Anmeldung diese Meldung:

Fehlermeldung You are not allowed to access this service

Im Reiter “Zugang” kannst du jederzeit wieder auf “Alle Benutzer” zurückstellen. Wichtig: Wählst du “Ausgewählte Benutzergruppen” und hakst keine Gruppe an, darf niemand mehr den Client nutzen.

Meine Apps

Unter “Meine Apps” sieht jeder Benutzer die Apps, für die er freigeschaltet ist, und öffnet sie mit einem Klick. Damit eine App dort einen Link bekommt, trägst du im Client unter “Allgemein” die “Client-Start-URL” ein, zum Beispiel https://paperless.{{base_domain}}:

Meine Apps mit Caddy Seite, Actual Budget, Forgejo und Paperless-ngx

So wird Pocket ID zum Startpunkt für deinen ganzen Server: einmal anmelden, dann alle Apps von einer Seite aus öffnen.

Weitere Einstellungen

Die wichtigsten Umgebungsvariablen für die .env von Pocket ID, wie sie in der Dokumentation stehen:

Variable Wirkung
ANALYTICS_DISABLED=true Schaltet die tägliche Zählung laufender Instanzen ab.
VERSION_CHECK_DISABLED=true Schaltet die Suche nach neuen Versionen auf GitHub ab.
STATIC_API_KEY Ein API-Schlüssel mit Adminrechten (mindestens 16 Zeichen), etwa für Skripte. Er wird im Header X-API-KEY mitgeschickt.
MAXMIND_LICENSE_KEY Lädt eine Geodatenbank für die Standortanzeige im Audit-Protokoll.
UI_CONFIG_DISABLED=true Nimmt die Einstellungen aus der Oberfläche aus Umgebungsvariablen. Nur dann wirken die SMTP-, LDAP- und ALLOW_USER_SIGNUPS-Variablen.
ALLOW_USER_SIGNUPS disabled, withToken (Registrierung über Einladungslinks) oder open.
SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_USER, SMTP_PASSWORD, SMTP_TLS Mailserver für Einmal-Anmeldelinks und Benachrichtigungen.
LDAP_ENABLED=true Synchronisiert Benutzer und Gruppen aus einem LDAP-Verzeichnis.

Das Audit-Protokoll zeigt unter “Persönlich” deine Aktivitäten und unter “Global” die aller Benutzer: Anmeldungen, neue Passkeys, Client-Freigaben. Mit TRUST_PROXY=true stehen dort die echten IP-Adressen. Der Reiter “Global” ist für Admins gedacht.

Backup und Update

Pocket ID speichert alles im Ordner data. Sichere ihn zusammen mit der .env, denn ohne ENCRYPTION_KEY ist die Datenbank nicht zu gebrauchen:

Terminalbash
cd ~
sudo tar czf pocketid-backup.tar.gz pocketid/data pocketid/.env

Zum Aktualisieren ziehst du neue Images und startest neu:

Terminalbash
cd ~/pocketid
sudo docker compose pull
sudo docker compose up -d

Die Version siehst du unten auf jeder Seite von Pocket ID. Vor größeren Sprüngen lohnt ein Blick in die Release Notes.

Fazit

Pocket ID macht aus vielen einzelnen Logins einen. Die Installation besteht aus einem Container, die Anmeldung läuft über Passkeys, und neue Apps hängst du in wenigen Minuten dran. Mit Gruppen steuerst du, wer was sehen darf, und mit oauth2-proxy bekommen sogar Seiten ohne eigenes Login einen Schutz.

Nutzt du schon eine zentrale Anmeldung für deine Server-Apps, und welche App würdest du als Nächstes anbinden? Schreib es gerne in die Kommentare.

FAQ

Häufige Fragen

Was ist Pocket ID?

Pocket ID ist ein selbst gehosteter OpenID-Connect-Anbieter, bei dem du dich ausschließlich mit Passkeys anmeldest. Andere Apps nutzen Pocket ID für ihre Anmeldung, du brauchst dich nur einmal zu authentifizieren.

Ist Pocket ID kostenlos?

Ja. Pocket ID ist Open Source. Für das Self-Hosting zahlst du nur für den Server.

Brauche ich zwingend eine Domain und HTTPS?

Ja. Browser erlauben Passkeys nur über HTTPS. Nur zum Ausprobieren funktioniert auch localhost.

Was passiert, wenn ich meinen Passkey verliere?

Du kommst nicht mehr an dein Konto. Hinterlege deshalb mindestens zwei Passkeys auf verschiedenen Geräten oder in einem Passwortmanager mit Synchronisierung.

Kann ich bestehende Passwort-Logins der Apps weiter nutzen?

Ja. Die Anmeldung über Pocket ID kommt als zusätzliche Option dazu. Das Passwortformular blendest du in den meisten Apps erst aus, wenn der SSO-Login sicher läuft.

Wie schütze ich eine App, die kein OIDC kann?

Setze oauth2-proxy davor und binde es in nginx oder einen anderen Reverse Proxy ein. Das Beispiel mit nginx und auth_request oben zeigt die komplette Konfiguration.

Warum bekomme ich in Paperless-ngx nach dem Login Fehler?

Neue Benutzer, die sich per Pocket ID anlegen, haben in Paperless zunächst keine Rechte. Gib dem Benutzer als Admin Rechte, setze ihn auf Superuser oder weise mit PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS automatisch eine Gruppe zu.

Wie aktualisiere ich Pocket ID?

Im Ordner mit der compose.yaml führst du sudo docker compose pull und danach sudo docker compose up -d aus. Deine Daten im Ordner data bleiben erhalten.