YinkoShield

Knowledge Center / Checkpoint architectures / checkpoint architectures · 2025·11

FIDO2 and passkeys — what the assertion does and does not prove

FIDO2 specifies a strong, phishing-resistant authentication primitive: an assertion produced by an authenticator over a server-issued challenge, bound to the relying-party identifier and the origin. Passkeys are the platform packaging of that primitive for cross-device usability. This page describes what the WebAuthn assertion proves cryptographically, and where the relying party still has to bind the body of the action separately.

[ FIDO2 / WebAuthn — three actors, one signature ] [ relying party ] issues challenge stores credential public key, credentialId, signature counter verifies assertion checks rpIdHash, origin, UV flag, counter monotonicity phishing-resistant [ user agent ] browser or platform binding builds clientDataJSON: type · challenge · origin forwards to authenticator no transaction body here [ authenticator ] user verification (UV) biometric / PIN / device unlock signs: authData ‖ SHA-256(clientData) credential private key stays in authenticator returns assertion challenge challenge assertion assertion passkeys = WebAuthn credentials with platform key-sync (Apple iCloud Keychain / Google Password Manager)
WebAuthn signs the challenge — present in clientDataJSON — bound to rpId and origin. The transaction body is not part of clientDataJSON unless a transaction-confirmation extension is in use.

1. Three actors, one signature

WebAuthn [1] is a three-actor protocol. The relying party server issues a challenge. The user agent — a browser, or a platform binding inside a native app — passes the challenge to the authenticator (a platform authenticator inside the device, or a roaming authenticator like a security key). The authenticator produces an assertion: a signature, with the credential’s private key, over the byte string authenticatorData ‖ SHA-256(clientDataJSON) [1, §6.3.3].

The relying party verifies the assertion using the public key it recorded at registration, checks four properties — the signature is valid, the authenticatorData.rpIdHash matches the expected relying-party identifier, the clientDataJSON.origin matches the expected origin, and the signature counter has strictly increased — and only then accepts the action.

This is enough to make the protocol phishing-resistant in the sense WebAuthn Level 2 §13 (Security Considerations) [1] and the FIDO2 architecture overview both define: the signature is only valid for the specific relying party and origin that issued the challenge. No replay across origins. No phishing site can elicit a valid assertion bound to the legitimate relying party. (Implementation gaps still exist around hybrid transport, RP-ID scoping, and conditional UI; the protocol’s phishing-resistance property is sound, deployment hardening is ongoing.)

2. What is in clientDataJSON

clientDataJSON is the structure the user agent constructs and the authenticator hashes into the signed payload. Per WebAuthn Level 2 [1, §5.8.1], it contains three load-bearing fields and an optional extensions block:

  • type"webauthn.get" for an assertion (or "webauthn.create" for a registration),
  • challenge — the server-issued nonce, base64url-encoded,
  • origin — the origin from which the request was made (https://example.com for web, or — for Android native-app integrations — an android:apk-key-hash:<…> origin bound to the RP ID via Digital Asset Links rather than a U2F-era AppID/facet construct),
  • crossOrigin — whether the request originated from a cross-origin frame.

(tokenBinding was carried in earlier WebAuthn drafts; Token Binding has been deprecated and is removed from WebAuthn Level 3 [4]. Production deployments do not rely on it.)

That is the structure. The transaction body — the amount, the beneficiary, the idempotency key, the resource being acted on — is not in clientDataJSON.

3. Passkeys — what they add and what they keep

Passkeys are the platform packaging of WebAuthn credentials with cross-device synchronisation [3]. Apple’s iCloud Keychain and Google Password Manager synchronise the credential as an end-to-end-encrypted blob across the user’s signed-in devices — the credential private key is reconstructed inside each device’s secure storage rather than transmitted in clear. The user can sign in on a new device without re-enrolling, and a synced passkey’s signature counter is typically reported as 0 across devices (the strict-monotonicity counter check is relaxed for synced passkeys per WebAuthn Level 3 guidance).

What passkeys add:

  • Cross-device usability. A credential created on one device is available on the user’s other signed-in devices.
  • Recovery. Re-installing the app or signing into a new device does not strand the user.
  • Cross-device authentication. A nearby phone can serve as the authenticator for a desktop browser via the hybrid transport (formerly cloud-assisted BLE).

What passkeys do not change about WebAuthn:

  • The same clientDataJSON structure is signed.
  • The transaction body is still not part of what the assertion signs.
  • The relying party still verifies the same four properties on every assertion.

The cryptographic primitive — challenge, signature, verification — is unchanged. Passkeys make it usable for consumer authentication; the security model on the wire is the same.

4. Where relying parties bind the action

WebAuthn Level 1 [1, §10.3] defined a transaction-confirmation extension family — txAuthSimple and txAuthGeneric — registered in the WebAuthn extensions registry at the time. The extensions were not retained in Level 2 and have negligible authenticator support today. CTAP [2] defines the on-the-wire extension transport, but the canonical txAuth definitions live in WebAuthn. The intent is constant: if the authenticator supports a transaction-confirmation extension and displays the transaction text on a surface it controls, the assertion can be made to sign the transaction text alongside the challenge.

A successor effort, W3C Secure Payment Confirmation (SPC), specifically targets transaction binding for payments — the relying party shows the user a payment instrument and amount on a browser-controlled surface, the authenticator signs an SPC-specific assertion bound to that displayed payload, and the issuer verifies. SPC is in early production deployment by some card schemes and is the standards-track answer to “WebAuthn does not bind the transaction body.”

In current production deployments — across consumer banking, payments, and corporate single sign-on — the extension is rarely used. Authenticator support is uneven and the display surface requirements are stringent. Most relying parties bind the action separately: a server-side state machine, an authenticated session bound to the prior assertion, a co-signed JWT, or a device-bound key in a separate stack.

This is not a flaw in WebAuthn. The protocol does what it specifies — who pressed — extremely well. The action — what they pressed for — is bound elsewhere, and that elsewhere is the operator’s design choice. Where Execution Evidence Infrastructure (EEI) — the device-identity infrastructure layer for banking and payments — sits in that design (signing the device-side flow between assertion and submission) is the subject of the the-fido2-submission-gap article in the prior theme.

5. Cross-references

6. External references

[1] W3C. Web Authentication: An API for accessing Public Key Credentials, Level 2. www.w3.org/TR/webauthn-2/. Cited 2025-11-18.

[2] FIDO Alliance. Client to Authenticator Protocol (CTAP) v2.1. fidoalliance.org/specs/fido-v2.1-ps-20210615/. Cited 2025-11-18.

[3] FIDO Alliance. Passkeys: passwordless authentication. fidoalliance.org/passkeys/. Cited 2025-11-18.

[4] W3C. Web Authentication, Level 3 (Editor’s Draft). w3c.github.io/webauthn/. Cited 2025-11-18.