major
#30074
React-Oberfläche: Anmeldung mit Passkeys (WebAuthn) - passwortlos oder als zweiter Faktor neben TOTP, Verwaltung der eigenen Passkeys in den Einstellungen
Kontext
Die Mehr-Faktor-Authentifizierung von TopLogic (#29092) kannte als zweiten Faktor nur ein zeitbasiertes Einmalpasswort (TOTP, RFC 6238) aus einer Authenticator-App (tl.accounts:Person#mfaSecret, #mfaRequirement). Passkeys (FIDO2/WebAuthn) - an ein Gerät, einen Sicherheitsschlüssel oder einen Passwort-Manager gebundene Schlüsselpaare, die der Browser mit PIN oder Biometrie freigibt - wurden nicht unterstützt.
Mit dem Modul tl-security-passkey meldet sich ein Anwender in der React-Oberfläche (.view.xml-Ebene) mit einem Passkey an. Die klassische Oberfläche bleibt bei Passwort und TOTP.
Verhalten
- Verwendungszweck je Anwendung: Die Anwendung legt mit der Option usage des PasskeyService fest, wofür Passkeys dienen, und bietet nur die passende Einrichtung an: login (Vorgabe) - Anmelden ohne Passwort, oder second-factor - Passkey als zweiter Faktor nach dem Passwort. Nie beides zugleich (#30118).
- Passwortlose Anmeldung (login): Das Anmeldeformular bietet unter den Single-Sign-On-Knöpfen und gleich breit wie diese "Mit Passkey anmelden". Der Browser fragt nach einem für die Anwendung registrierten Passkey (auffindbare Zugangsdaten, kein Benutzername nötig); nach der Freigabe am Gerät ist der Anwender angemeldet. Passkeys werden auffindbar und immer mit Benutzerverifikation (PIN/Biometrie) angelegt; die Anmeldung gilt damit als zwei Faktoren und erfüllt MfaRequirement required. Ein Passkey ist hier kein zweiter Faktor: Nach der Anmeldung mit Passwort wird kein Passkey verlangt.
- Zweiter Faktor (second-factor): Das Anmeldeformular bietet keine Anmeldung mit Passkey. Passkeys werden nicht auffindbar angelegt. Ein Konto mit Passkey bestätigt die Anmeldung nach dem Passwort mit dem Passkey; ein Konto mit Passkey und Authenticator-App wählt in einem Auswahldialog. Ein Konto mit required ohne zweiten Faktor wählt bei der Anmeldung, ob es eine Authenticator-App oder einen Passkey einrichtet; das Anlegen des Passkeys schließt die Anmeldung ab.
- Der Dienst weist eine Zeremonie ab, die nicht zum Verwendungszweck passt. Gespeicherte Passkeys bleiben bei einem Wechsel erhalten; ein als zweiter Faktor angelegter Passkey meldet nach dem Wechsel auf login nicht ohne Passwort an, der Anwender legt dann einen neuen an.
- Adresse der Anwendung: Passkeys sind an die öffentliche Adresse der Anwendung gebunden (%HOST%, gesetzt über die Variable tl_host, z.B. https://apps.example.com); die Adresse einer einzelnen HTTP-Anfrage spielt keine Rolle, sodass Passkeys auch hinter einem Reverse-Proxy funktionieren, der TLS terminiert. Ist die Adresse nicht gesetzt, meldet die Anwendung dies beim Start als Fehler und bietet keine Passkeys an.
- Falsche oder unsichere Seite: Öffnet der Anwender die Anwendung unter einer anderen Adresse als der eingerichteten, sind die Passkey-Knöpfe von vornherein gesperrt und nennen im Tooltip die Adresse, unter der Passkeys funktionieren; es entsteht kein Passkey, den die Anwendung nicht kennt. Auf einer über http mit Rechnernamen geöffneten Seite erklärt der Tooltip: „Passkeys erfordern eine sichere Verbindung. Öffnen Sie die Anwendung über HTTPS (oder auf localhost).“
- Meldungen: Eine abgewiesene Registrierung oder Anmeldung nennt den Grund: falsche Adresse, der Anwendung unbekannter Passkey („Dieser Passkey ist bei dieser Anwendung nicht registriert. Möglicherweise wurde er nicht vollständig eingerichtet, gelöscht oder gehört zu einer anderen Installation. …“), Passkey eines anderen Kontos, Benutzer nicht verifiziert, Signaturzähler, abgelaufene Anfrage. Fehler, die Browser oder Authenticator melden, erscheinen mit dem Text des Browsers. Jede Abweisung wird als Warnung mit Grund, Credential-ID, Konto und Adresse der Seite protokolliert.
- Aufräumen im Passwort-Manager: Scheitert eine Registrierung, nachdem der Browser den Passkey bereits gespeichert hat, oder legt der Browser bei der Anmeldung einen der Anwendung unbekannten Passkey vor (z.B. einen gelöschten), teilt die Anwendung dies dem Browser mit (WebAuthn Signal API), und der Passwort-Manager entfernt ihn. Nach einer Anmeldung mit Passkey erhält der Browser die Liste der gültigen Passkeys des Kontos. Browser ohne Signal API ignorieren dies.
- Einstellungen → Sicherheit ist in Abschnitte gegliedert, jeweils Überschrift, aktueller Stand, Aktionen: Passwort (Passwort ändern), Stellvertreter (Stellvertreter, vertretene Personen, Stellvertreter benennen) und Zwei-Faktor-Authentifizierung mit dem Stand "Aktiv: Authenticator-App, Passkey" bzw. "Nicht aktiv" und dem Unterblock Authenticator-App (eingerichtet?, Einrichten/Abschalten). Die eigenen Passkeys (Name, angelegt, zuletzt verwendet; Hinzufügen, Umbenennen, Löschen) stehen bei login in einem eigenen Abschnitt Anmelden mit Passkey nach Passwort, bei second-factor als Unterblock Passkeys der Zwei-Faktor-Authentifizierung. Hinzufügen und Löschen eines Passkeys verlangen das Passwort (<verify-identity>). Der letzte zweite Faktor eines Kontos mit required lässt sich nicht löschen; der Löschen-Knopf ist dann gesperrt und nennt den Grund.
- Authenticator-App abschalten: Ein Konto mit required darf die Authenticator-App abschalten, solange ein anderes Verfahren eingerichtet bleibt; die Anforderung bleibt required.
- Verwaltung durch den Administrator: Die Kontenverwaltung (admin/access-control/accounts.view.xml) zeigt die Passkeys eines Kontos und kann sie löschen (ohne Letzter-Faktor-Regel: das Konto richtet bei der nächsten Anmeldung einen neuen Faktor ein).
- Ein Konto mit disabled kann bei second-factor keine Passkeys anlegen; bereits vorhandene Passkeys bleiben verwendbar (wie ein gespeichertes TOTP-Geheimnis). Bei login ist der Passkey ein Anmeldeverfahren wie das Passwort und auch einem Konto mit disabled erlaubt.
Technische Umsetzung
- Bibliothek: Die WebAuthn-Zeremonien (Challenge, Origin- und RP-ID-Prüfung, Signatur, Signaturzähler) übernimmt Yubico java-webauthn-server (com.yubico:webauthn-server-core 2.10.1, Apache-2.0), Version in tl-bom (dazu com.upokecenter:cbor, das die Bibliothek als Versionsbereich deklariert). Angeboten werden die Signaturalgorithmen ES256, EdDSA, ES384, ES512 und RS256.
- Generisches Client-Element in com.top_logic.layout.react: ReactWebAuthnControl / TLWebAuthn ist ein gewöhnlicher Knopf, der navigator.credentials.create / get mit den Optionen des Servers direkt im Klick-Handler ausführt (Safari verlangt die Benutzergeste) und das Ergebnis an einen WebAuthnListener meldet. Vor Ablauf der Optionen holt es selbsttätig neue (refresh); eine unsichere Seite oder ein Browser ohne WebAuthn wird beim Anzeigen gemeldet (unavailable), ebenso die Origin der Seite (origin, WebAuthnListener#onPageOrigin). Ein verborgener Knopf zeichnet nichts und startet nichts. signalUnknownCredential / signalAllAcceptedCredentials rufen die gleichnamigen Funktionen der WebAuthn Signal API im Browser auf (einmaliges FunctionCall-Ereignis an TLReact.webAuthnSignal). Fehler meldet der Besitzer über ViewMessages.
- Austauschbarer zweiter Faktor in com.top_logic.layout.view: SecondFactor (eingerichtet? / einrichtbar? / bestätigen / einrichten), SecondFactorsConfig (Liste, Module tragen über autoconf bei), SecondFactorStep (Entscheidung und Auswahldialog second-factor-choice.view.xml), TotpSecondFactor. LoginAction kennt Passkeys nicht. TL-Script secondFactorEnrolled(account) für die Statuszeile; DisableMfaAction prüft die übrigen konfigurierten Verfahren.
- TypeObservingRule (com.top_logic.layout.view): eine intrinsische Ausführbarkeitsregel, die weitere Objekte liest, entscheidet bei deren Änderung neu (Gegenstück zu observed-types). Genutzt von <delete-passkey/>.
- Leere Elemente nehmen keinen Platz ein: Ein <switch> ohne zutreffenden Fall (und <visible-if> im unsichtbaren Zustand) verbirgt sich, eine leere <object-list> zeigt nur ihren Leertext, TLStack, TLGrid und TLDeckPane beachten das hidden-Flag, <login-commands> und <login-methods> sind ohne Inhalt verborgen. Vorher hinterließen sie leere Elemente, die in ihrem Container einen zusätzlichen Abstand erzeugten.
- Erweiterungspunkte: login-form.view.xml trägt die Ids credentials und login-methods, der Reiter "Sicherheit" von settings-tabs.view.xml die Ids security-sections, password-section, deputies-section, two-factor-section, two-factor-methods und authenticator-app; tl-security-passkey erweitert beide Views per Overlay.
- Modul tl-security-passkey: Typ tl.security.passkey:Passkey (Credential-ID mit eindeutigem Index, COSE-Public-Key, Signaturzähler, Transports, Backup-Flags, Name, Anlage- und Verwendungsdatum) als Komposition tl.accounts:Person#passkeys; Rechte über das Konto (access-parent). PasskeyService mit RelyingParty und CredentialRepository. Die Relying Party wird einmal beim Start aus %HOST% bestimmt (RelyingPartyAddress: Origin = %HOST% normalisiert, RP-ID = dessen Rechnername), nie aus einer Anfrage; rp-id (z.B. übergeordnete Domain) und origins (ersetzen die Origin aus %HOST%) übersteuern, eine RP-ID muss Rechnername oder übergeordnete Domain jeder Origin sein. Weitere Konfiguration usage (PasskeyUsage login / second-factor; bestimmt residentKey und Benutzerverifikation der Registrierung und die angenommenen Anmeldungen), rp-name, user-verification (bei second-factor), timeout. Abweisungen als PasskeyRefusedException mit Kategorie und Credential-ID; die Kategorie bestimmt der Dienst durch eigene Prüfungen vor der Bibliothek. User-Handle ist die Objekt-ID des Kontos. View-Elemente <passkey-login> (verbirgt sich, wenn der Verwendungszweck seine Art der Anmeldung nicht erlaubt), <passkey-registration> (complete-login, <on-success>) und <if-passkey-usage usage="…"> (zeigt seinen Inhalt nur beim angegebenen Verwendungszweck), PasskeyCeremony (Prüfung der Origin der Seite, Signale an den Browser), PasskeySecondFactor (nur bei second-factor), <delete-passkey/>, TL-Script passkeyMayUse(account), Overlays des Anmeldeformulars, der Einstellungen und der Kontenverwaltung. tl-demo-react bindet das Modul ein.
- Behoben: Person.setMFASecret schrieb das Geheimnis in das Attribut mfaRequirement statt mfaSecret.
- Dokumentation: Entwicklerartikel authentication/passkeys (tl-security-passkey), überarbeitet authentication/mfa, authentication, view-layer/commands und view-layer/overlays.
Nicht Teil dieses Tickets: Passkey-Vorschläge im Autofill des Benutzernamenfelds (autocomplete="username webauthn"), Attestierungsprüfung / Geräte-Allowlists, Wiederherstellungscodes, die klassische Oberfläche.
Verifikation
Tests: TestReactWebAuthnControl, webauthn-json.test.ts, webauthn-signals.test.ts, options-expiry.test.ts, hidden-containers.test.tsx, TLWebAuthn.test.tsx, TestSecondFactorStep, TestDisableMfaAction, TestSecondFactorFunctions, TestSwitchElement, TestObjectListItems, TestRelyingPartyAddress, TestPasskeyService (Registrierung und Anmeldung mit einem Software-Authenticator je Verwendungszweck; Relying Party aus %HOST%, fehlende Adresse, falsche Origin, unbekannter Passkey, wiederholte Antwort, fremdes Konto, fehlende Benutzerverifikation, Zählerrückschritt, Anmeldung des falschen Verwendungszwecks), TestPasskeyCeremony (u.a. Origin der Seite, Signale, Knopf je Verwendungszweck), TestPasskeyUsageElement, TestDeletePasskeyAction, TestPasskeyFunctions, TestPerson#testSetMFASecret. In tl-demo-react (/view/, root / root1234) mit dem virtuellen Authenticator von Chrome geprüft, in beiden Verwendungszwecken: Einrichtung bei der Anmeldung, zweiter Faktor, passwortlose Anmeldung, Abbruch, Anmeldung nach über 5 Minuten offenem Formular, gesperrter Knopf auf einer http-Seite und unter einer anderen Adresse (127.0.0.1 statt localhost), Anmeldung mit einem auf dem Server gelöschten Passkey (Meldung, Warnung im Protokoll, Passkey vom Authenticator entfernt), Hinzufügen/Umbenennen/Löschen in den Einstellungen, Letzter-Faktor-Regel, Löschen durch den Administrator, Abstände im Anmeldeformular und im Reiter "Sicherheit".