Anmeldung per Passkey (WebAuthn)
CI / pruefen (pull_request) Canceled after 0s

Ein Passkey ist der dritte Weg in denselben Trichter: finishLogin() prueft die
Antwort und gibt die Kennung zurueck, der Endpunkt legt damit ueber
createSession() dieselbe Sitzung an wie bei Passwort und OIDC. Die
Sitzungsschicht bleibt unangetastet.

Die Trennung zwischen Pruefung und Sitzung ist Absicht: in passkeys.ts steht
nur WebAuthn, das Setzen von Cookie und Sitzungszeile gehoert zur HTTP-Schicht.

Die Challenge liegt in einem signierten, fuenf Minuten gueltigen httpOnly-
Cookie statt in einer Tabelle — das spart Schema und Aufraeumjob fuer alles,
was liegen bleibt, wenn jemand den Dialog abbricht.

Die RP ID kommt aus APP_URL, nicht aus dem Host-Header. Hinter einem Reverse
Proxy hinge sie sonst am falschen Namen, und ein Passkey ist fest an diesen
Namen gebunden. Ueber nacktes HTTP verschwindet die Verwaltung mit einem
Hinweis statt einen kryptischen Browserfehler zu zeigen.

Den letzten verbleibenden Anmeldeweg laesst sich nicht entfernen: wer kein
Passwort und keine OIDC-Verknuepfung hat, behaelt seinen letzten Passkey.

Die Bibliothek prueft den Signaturzaehler selbst und wirft dabei gewoehnliche
Error. Die werden jetzt uebersetzt, damit eine erwartete Ablehnung nicht als
unerwarteter Serverfehler im Log landet. Die eigene Zaehlerpruefung bleibt als
zusaetzliche Sicherung fuer den Fall, den die Bibliothek durchlaesst.

Geprueft mit virtuellem Authenticator gegen einen Produktionsbau: anlegen,
abmelden, Anmeldung allein per Passkey, Sitzungscookie gesetzt,
Challenge-Cookie verbraucht, Passwortweg unveraendert. Mit kuenstlich
hochgesetztem Zaehler wird die Anmeldung abgelehnt und der Zaehler bleibt
stehen. Dazu sieben Tests fuer Verwaltung, Rechte und die Aussperrsperre.

Refs #6
This commit is contained in:
Florian Wilde
2026-09-02 07:04:45 +02:00
parent 58a1d177df
commit c0579c6f61
17 changed files with 1381 additions and 6 deletions
+25
View File
@@ -178,6 +178,31 @@ mit Erklärungen steht in [`.env.example`](.env.example). Die wichtigsten:
| `ENABLE_SCHEDULER` | Eingebauter Hintergrundlauf für Sync und Benachrichtigungen |
| `SMTP_*` | E-Mail-Versand |
| `OIDC_*` | Optionale SSO-Anbindung (Authelia, Keycloak, Authentik …) |
| `ENABLE_PASSKEYS` | Anmeldung per Passkey (Standard `true`) |
### Passkeys
Statt Passwort geht auch ein Passkey — Windows Hello, Face ID, Touch ID oder ein
Sicherheitsschlüssel. Anlegen unter **Einstellungen → Passkeys**, anmelden über
„Mit Passkey anmelden" ohne vorherige E-Mail-Eingabe. Das Passwort bleibt
daneben bestehen; den letzten verbleibenden Anmeldeweg lässt Flimmer nicht
entfernen.
Drei Dinge, die beim Selbsthosten zählen:
- **HTTPS ist Pflicht.** Der Browser gibt Passkeys nur über eine verschlüsselte
Verbindung frei; einzige Ausnahme ist `localhost`. Läuft deine Instanz über
einfaches HTTP, verschwindet die Passkey-Verwaltung mit einem Hinweis, statt
einen kryptischen Browserfehler zu zeigen.
- **Der Hostname kommt aus `APP_URL`**, nicht aus dem `Host`-Header. Hinter
einem Reverse Proxy muss `APP_URL` also stimmen, sonst hängen die Passkeys am
falschen Namen.
- **Ein Domainwechsel entwertet alle Passkeys.** Wer von `flimmer.fritz.box` auf
`flimmer.example.com` umzieht, muss sie neu anlegen — sie sind fest an den
Hostnamen gebunden. Halte vorher einen zweiten Anmeldeweg bereit.
Ein Passkey, der nur auf einem Gerät liegt (nicht synchronisiert), ist mit dem
Gerät verloren. Die Übersicht in den Einstellungen sagt dazu, was Sache ist.
## Entwicklung