Bei FIDO2 vs. WebAuthn geht es um Schichten, nicht um eine Wahl. WebAuthn (W3C Web Authentication) definiert die JavaScript-API, die Relying Parties aufrufen, und die signierten Datenformate, die sie prüfen. CTAP (FIDO Client to Authenticator Protocol) legt fest, wie ein Browser oder Betriebssystem mit einem externen Authenticator kommuniziert: über USB, NFC, Bluetooth oder den hybriden Transport per QR-Code. FIDO2 ist der 2018 eingeführte Oberbegriff der FIDO Alliance für WebAuthn plus CTAP 2. Passkeys sind FIDO-Credentials, die über diese Standards erzeugt werden.
Auf einen Blick
| WebAuthn | CTAP | FIDO2 | |
|---|---|---|---|
| Was es ist | Browser-API und Datenformate | Übertragungsprotokoll zu Roaming-Authenticators | Oberbegriff für beide |
| Herausgeber | W3C | FIDO Alliance | FIDO Alliance |
| Aktuelle Version | Level 3, W3C Recommendation vom 25. August 2026 | CTAP 2.3, Proposed Standard vom 26. Februar 2026 (2.2: Juli 2025) | WebAuthn Level 3 mit CTAP 2.3 |
| Frühere Versionen | Level 1 (März 2019), Level 2 (April 2021) | CTAP 2.0, 2.1 (2021); CTAP1 ist U2F | U2F (2014) ist der Vorgänger |
| Wer es implementiert | Browser und Plattformen (API), Relying Parties (Prüfung auf dem Server) | Browser, Betriebssysteme, Sicherheitsschlüssel, Smartphones | Alle in der Kette |
| Zwischen | Webseite und Browser; Server und Authenticator-Daten | Client-Plattform und Authenticator | Von der Relying Party bis zum Authenticator |
| Kodierung | JavaScript-Objekte, clientDataJSON, CBOR-Attestierungsobjekte | Befehle in kanonischem CBOR nach CTAP2, etwa authenticatorGetAssertion (0x02) | Beides |
| Transporte | Entfällt | USB HID, NFC, Bluetooth Low Energy, hybrid (QR-Code, Näheprüfung, Tunnel) | Beides |
| Wichtigste Einstiegspunkte | navigator.credentials.create() und .get() | authenticatorMakeCredential (0x01), authenticatorGetInfo (0x04) | Entfällt |
Wer bei einer Anmeldung was tut
- Die Website ruft
navigator.credentials.get()mit einer Challenge und ihrer Relying-Party-ID auf. Das ist WebAuthn. - Der Browser baut
clientDataJSON(Typ, Challenge, Origin, Cross-Origin-Flag), hasht es und fordert einen Authenticator zum Signieren auf. Bei einem integrierten Plattform-Authenticator (Touch ID, Windows Hello, Android) läuft das über APIs des Betriebssystems; bei einem Sicherheitsschlüssel oder Smartphone über CTAP als BefehlauthenticatorGetAssertionin CBOR. - Der Authenticator prüft die Benutzeranwesenheit und, falls angefordert, die Benutzerverifizierung und signiert dann seine Authenticator-Daten, verkettet mit dem Hash der Client-Daten. Die Antwort geht über CTAP oder die Plattform-API zurück.
- Der Browser gibt die Bytes an die Seite zurück, und der Server prüft sie so, wie es WebAuthn §7.2 festlegt.
Das folgende Skript parst den offiziellen Testvektor §16.2 aus WebAuthn Level 3, der eine Registrierung und eine Authentifizierung für ein ES256-Credential enthält, und prüft die Signatur (Node.js 26, cbor-x 1.6.0). authData und die Signatur hat der Authenticator erzeugt, clientDataJSON der Browser.
import { createHash, createPublicKey, verify } from "node:crypto";
import { decode } from "cbor-x";
// WebAuthn Level 3 §16.2 test vector: ES256 credential, "none" attestation
const hex = (s) => Buffer.from(s, "hex");
const attestationObject = hex("a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220");
const authenticatorData = hex("bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b51900000000");
const clientDataJSON = hex("7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224f63446e55685158756c5455506f334a5558543049393770767a7a59425039745a63685879617630314167222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73657d");
const signature = hex("3046022100f50a4e2e4409249c4a853ba361282f09841df4dd4547a13a87780218deffcd380221008480ac0f0b93538174f575bf11a1dd5d78c6e486013f937295ea13653e331e87");
const sha256 = (b) => createHash("sha256").update(b).digest();
const flagNames = { 0x01: "UP", 0x04: "UV", 0x08: "BE", 0x10: "BS", 0x40: "AT", 0x80: "ED" };
const flags = (b) => Object.entries(flagNames).filter(([bit]) => b & bit).map(([, n]) => n).join(" ");
// Registration: authenticator data produced by the authenticator (CTAP authenticatorMakeCredential)
const { fmt, authData } = decode(attestationObject);
const credIdLen = authData.readUInt16BE(53);
const coseKey = decode(authData.subarray(55 + credIdLen));
console.log("attestation format:", fmt);
console.log("rpIdHash == SHA-256('example.org'):", authData.subarray(0, 32).equals(sha256("example.org")));
console.log("flags:", flags(authData[32]), "| signCount:", authData.readUInt32BE(33));
console.log("AAGUID:", authData.subarray(37, 53).toString("hex"), "| credential ID bytes:", credIdLen);
console.log("COSE key: kty", coseKey[1], "alg", coseKey[3], "crv", coseKey[-1]);
// Authentication: verify the assertion with the registered public key
const publicKey = createPublicKey({ key: { kty: "EC", crv: "P-256",
x: Buffer.from(coseKey[-2]).toString("base64url"), y: Buffer.from(coseKey[-3]).toString("base64url") }, format: "jwk" });
console.log("\nclientDataJSON (built by the browser):", clientDataJSON.toString());
console.log("assertion flags:", flags(authenticatorData[32]));
const ok = verify("sha256", Buffer.concat([authenticatorData, sha256(clientDataJSON)]), publicKey, signature);
console.log("signature over authenticatorData || SHA-256(clientDataJSON):", ok ? "valid" : "invalid");
attestation format: none
rpIdHash == SHA-256('example.org'): true
flags: UP BE BS AT | signCount: 0
AAGUID: 8446ccb9ab1db374750b2367ff6f3a1f | credential ID bytes: 32
COSE key: kty 2 alg -7 crv 1
clientDataJSON (built by the browser): {"type":"webauthn.get","challenge":"OcDnUhQXulTUPo3JUXT0I97pvzzYBP9tZchXyav01Ag","origin":"https://example.org","crossOrigin":false}
assertion flags: UP BE BS
signature over authenticatorData || SHA-256(clientDataJSON): valid
Die Flags zeigen Benutzeranwesenheit (UP), ein Credential, das Backup-fähig ist und gesichert wurde (BE, BS: ein synchronisierter Passkey), sowie bei der Registrierung attestierte Credential-Daten (AT). Der COSE-Schlüssel (Schlüsseltyp 2 = EC2, Algorithmus −7 = ES256, Kurve 1 = P-256) ist das Format, das beide Spezifikationen gemeinsam nutzen; der WebAuthn-Inspektor dekodiert dieselben Strukturen aus einer echten Browser-Antwort.
Von U2F zu Passkeys
FIDO U2F (2014) gab Sicherheitsschlüsseln ein einfaches Protokoll für den zweiten Faktor, heute CTAP1 genannt, und eine Browser-API. WebAuthn ersetzte diese API 2018–2019 (Chrome 67, Firefox 60, Safari 13), und CTAP 2 brachte PINs, eingebaute Benutzerverifizierung und Discoverable Credentials, sodass ein Schlüssel das Passwort ersetzen konnte, statt es nur zu ergänzen. Chrome entfernte die alte JavaScript-API für U2F in Version 104 (2022); darüber registrierte Credentials funktionieren in WebAuthn weiterhin über die Erweiterung appid.
Als Apple, Google und Microsoft 2022 synchronisierte Passkeys ankündigten, brauchte es dafür kein neues Protokoll: Ein Passkey ist ein Discoverable Credential mit gesetzten Backup-Flags, das über dieselbe WebAuthn-API erzeugt und verwendet wird. CTAP 2.2 (Juli 2025) standardisierte den hybriden Transport, mit dem ein Smartphone nach dem Scannen eines QR-Codes als Authenticator für einen Computer in der Nähe dient, sowie Richtlinien zur PIN-Komplexität; CTAP 2.3 (Februar 2026) ist eine inkrementelle Überarbeitung, die zum Beispiel Authenticators melden lässt, welche Konfigurationsbefehle sie unterstützen.
Attestierung und Zertifizierung
Bei der Registrierung kann ein Authenticator ein Attestierungs-Statement mitliefern, signiert mit einem Schlüssel, den eine ganze Charge von Geräten teilt, sowie eine AAGUID, die das Modell identifiziert. WebAuthn definiert die Formate des Statements (packed, tpm, android-key, apple, fido-u2f, none); die FIDO Alliance zertifiziert Authenticators und veröffentlicht ihre Metadaten, darunter Attestierungs-Root-Zertifikate und Zertifizierungsstufen, im FIDO Metadata Service. Die meisten Websites für Endkunden sollten keine Attestierung anfordern und jeden Authenticator akzeptieren; Unternehmen, die nur zertifizierte Hardware-Schlüssel zulassen dürfen, fordern eine Attestierung vom Typ direct oder enterprise an und gleichen sie mit den Metadaten ab, wie der YubiKey-Attestierungs-Verifizierer und der AAGUID-Lookup zeigen.
Was du implementieren musst
- Website oder Web-App: WebAuthn im Browser und eine Serverbibliothek, die die Prüfschritte aus §7 umsetzt.
- Native App für iOS oder Android: die Passkey-APIs der Plattform (AuthenticationServices, Credential Manager), die Daten im WebAuthn-Format liefern, gebunden an deine Web-Domain über Associated Domains oder Digital Asset Links.
- Unternehmen, die nur Hardware-Schlüssel zulassen: WebAuthn mit Attestierung, abgeglichen mit Einträgen im FIDO Metadata Service.
- Hersteller von Sicherheitsschlüsseln, Smartphones oder Betriebssystemen: CTAP für Roaming-Authenticators, FIDO-Zertifizierung für das Produkt.
- Beschaffung: Frage nach der FIDO-Zertifizierungsstufe, der unterstützten CTAP-Version und den Transporten sowie nach den WebAuthn-Funktionen, auf die du angewiesen bist, statt nur nach „FIDO2-Unterstützung“.
Hinweise zur Migration
- U2F-Registrierungen ablösen: Sende bei der Anmeldung weiterhin die alte App-ID über die Erweiterung
appidund registriere bei der nächsten Anmeldung des jeweiligen Nutzers ein neues WebAuthn-Credential. - Passkeys einführen: Fordere
residentKey: "required"unduserVerification: "preferred"oder"required"an und speichere die Backup-Flags. - Umstieg auf Level 3, Funktion für Funktion: Die bedingte Erstellung (Conditional Create),
getClientCapabilities(), die Signal-Methoden und Related Origins fallen ohne Unterstützung alle sauber auf das bisherige Verhalten zurück. - Tests: Probiere vor dem Start in der Passkey-Sandbox Plattform-Authenticators, einen USB- oder NFC-Schlüssel und den hybriden Ablauf mit einem Smartphone aus.
Quellen
- W3C Web Authentication Level 3 (§6.1 Authenticator-Daten, §7 Operationen der Relying Party, §16 Testvektoren)
- Spezifikation FIDO Client to Authenticator Protocol 2.3
- FIDO Client to Authenticator Protocol 2.2 (§6 Befehle, §11 Anbindung an die Transporte)
- FIDO Metadata Service
- RFC 9052, COSE-Strukturen und -Verarbeitung und RFC 9053, COSE-Algorithmen