SimpleWebAuthn teilt WebAuthn auf zwei Pakete auf: @simplewebauthn/browser kapselt navigator.credentials, und @simplewebauthn/server erzeugt die Optionen, die eine Relying Party sendet, und prüft, was zurückkommt – Attestierungsformate, COSE-Schlüssel, Zähler und Signaturen. Für die Prüfung wurde das Server-Paket mit einem Software-Authenticator angesteuert, der Standard-JSON für Registrierung und Assertion erzeugte – dieselben Bytes wie für die Seiten zu Python, Go und Java.
Der häufigste Integrationsfehler ist eine Abweichung zwischen dem, was die Optionen anfordern, und dem, was die Prüfung verlangt. generateRegistrationOptions() setzt standardmäßig userVerification: "preferred", beide Verifizierer dagegen standardmäßig requireUserVerification: true: Eine Assertion von einem Authenticator, der nur die Benutzeranwesenheit nachwies – ein Sicherheitsschlüssel ohne PIN –, wurde mit User verification required, but user could not be verified abgelehnt. Entscheide dich einmal und sorge dafür, dass beide Seiten dasselbe verlangen, wie es das Beispiel tut.
Assertion-Signaturen für ES256 müssen ASN.1-DER sein, wie es die WebAuthn-Spezifikation verlangt. Eine rohe r‖s-Signatur mit 64 Byte landet trotzdem im ASN.1-Parser und scheitert mit einer Meldung, die von den Signaturbytes abhängt – Too big integer, End of input reached before message was fully decoded oder Data does not match to ECDSASigValue ASN1 schema. waren bei 60 frischen Credentials die häufigsten –, und keine davon erwähnt die Kodierung. Authenticators und Passkey-Anbieter erzeugen DER; rohe Signaturen deuten daher meist auf einen selbst geschriebenen Client oder eine Testumgebung hin, die sign() von WebCrypto direkt aufgerufen hat. Konvertiere sie mit dem Konverter DER ⇄ Raw. Eine DER-Signatur mit High-S bestand die Prüfung.
Ein Signaturzähler unter dem gespeicherten Wert ist ein Fehler, keine Warnung: Die Prüfung warf Response counter value 3 was lower than expected 5, und ein Zählerstand von 0 nach einer gespeicherten 5 scheiterte genauso. Nur ein Credential, das immer 0 gemeldet hat – wie es synchronisierte Passkeys tun –, besteht mit 0. Speichere den empfangenen Zählerstand und entscheide selbst, ob ein Rückgang das Credential sperrt oder nur eine Benachrichtigung auslöst. Version 14 führt in der Standardliste pubKeyCredParams ML-DSA-44 (COSE -48) an erster Stelle, vor Ed25519 (-8), ES256 (-7) und RS256 (-257), und das Ed25519-Credential funktionierte durchgängig, von der Registrierung bis zur Prüfung.
Dekodiere mit dem WebAuthn-Inspektor, was ein Browser tatsächlich gesendet hat, probiere Abläufe ohne echten Authenticator in der Passkey-Sandbox aus und bestimme Authenticator-Modelle anhand der AAGUID mit dem AAGUID-Lookup.