Presentation Flow
After issuance, the user holds a credential in their cloud wallet. Presentation is the act of sharing it with a verifier — proving two things at once: (a) possession of the credential, and (b) control of the DID it was issued to. Both come from a single FIDO-signed VP.
Sequence
Section titled “Sequence”
Preconditions on the verifier
Section titled “Preconditions on the verifier”The verifier must support the fido4vc-jcs-2026 cryptosuite. In our reference deployment, that’s done by walt.id’s signature_ld-vp policy which delegates to the verifier sidecar over HTTP. Other verifier stacks can integrate by either:
- Running the sidecar and adding an HTTP-delegation policy, or
- Adopting a native implementation of the cryptosuite (TypeScript or Kotlin reference impls available)
See the Integration Guide for the verifier-side wiring.
Phases
Section titled “Phases”The flow has three logical phases.
Phase 1 — Presentation Request (steps 1–3)
Section titled “Phase 1 — Presentation Request (steps 1–3)”The verifier kicks off an OpenID4VP authorization request. Two transport variants:
- Cross-device. Verifier displays a QR code on (say) a kiosk; user scans it with their phone. Most common in physical-presence scenarios (border check, age verification at a bar).
- Same-device. Verifier redirects the user’s browser directly to the wallet. Most common in pure-web scenarios.
Either way, the verifier sends an authorization request containing response_uri, nonce, and state. The wallet backend receives it.
Phase 2 — VP Token Creation with Proof (steps 3.1–3.15)
Section titled “Phase 2 — VP Token Creation with Proof (steps 3.1–3.15)”The wallet backend assembles the VP that will be presented.
- Resolve presentation requirements. Read the presentation definition from the verifier’s request — which credential types are needed, what attributes must be disclosed.
- Select credential and DID. The wallet selects a matching credential and its bound DID.
- Prepare unsigned VP. Construct the VP envelope (
@context, typeVerifiablePresentation, holder DID, embedded VCs) and the unsignedldp_vpproof structure (type, cryptosuitefido4vc-jcs-2026, verificationMethod, proofPurposeauthentication, challenge from the verifier’snonce, domain, created). - Canonicalize and hash. JCS-canonicalize the VP-without-proof and the proof-options separately. Concatenate and SHA-256. This is the WebAuthn challenge.
- FIDO assertion. The middleware invokes WebAuthn with this challenge. User authenticates. The authenticator signs
authenticatorData ‖ SHA-256(clientDataJSON). - Assemble proofValue. The wallet backend folds the WebAuthn outputs (signature, authenticatorData, clientDataJSON) into the proof’s
proofValueand produces the final VP.
Phase 3 — VP Token Delivery (steps 4–8)
Section titled “Phase 3 — VP Token Delivery (steps 4–8)”The wallet submits the signed VP to the verifier.
- Cross-device: sent as an OpenID4VP authorization response to
response_uri. The verifier replies withredirect_uri+response_code. The wallet redirects the user’s browser back to the original site (now withresponse_code), where the verifier fetches the actual response data. - Same-device: the response is a direct redirect with the VP token embedded.
The verifier:
- Resolves the
fido4vc-jcs-2026cryptosuite handler. - Recomputes the expected challenge from the canonicalized VP.
- Parses
clientDataJSONfromproofValueand asserts the embedded challenge matches. - Resolves the
verificationMethodDID to the signer’s JWK. - Recomputes the WebAuthn signed bytes (
authenticatorData ‖ SHA-256(clientDataJSON)) and verifies the ECDSA signature. - Validates the embedded VCs (issuer signature, expiration, revocation, presentation-definition predicates).
- Returns the verification result.
What’s distinctive about this flow
Section titled “What’s distinctive about this flow”One FIDO signature, two proofs. The same WebAuthn assertion simultaneously proves DID control (because the signature verifies against the DID’s embedded public key) and is bound to this specific VP (because the challenge inside clientDataJSON equals the canonicalized VP hash). The verifier gets both facts for the cost of one user interaction.
The nonce is in the document, not separate. OpenID4VP’s nonce ends up inside the VP’s proof.challenge field, which is part of the canonicalized hash that feeds the WebAuthn challenge. So the verifier-provided nonce is cryptographically tied into the signature itself, not just transmitted alongside it.
Cross-device and same-device share the same proof. The proof is the same structure regardless of how the VP gets delivered. The two paths differ only in HTTP-level routing.
Failure modes
Section titled “Failure modes”- Verifier doesn’t support
fido4vc-jcs-2026. The presentation will be rejected by the verifier’s signature policy. Diagnose by inspecting the verifier’s enabled policies and confirming asignature_ld-vp(or equivalent) policy is registered. - Wallet picks a credential issued to a different DID. The verifier won’t be able to match the DID in
holderwith the DID in the credential’s subject. Wallet implementations must filter credentials by their bound DID before presentation. clientDataJSON.originmismatch. Standard WebAuthn validation rejects this — the assertion was created for one origin but presented from another. Usually a configuration error in the relying party / FIDO middleware.- Replay attempt. The verifier-issued
nonceis single-use; the verifier rejects a second submission. The wallet should not cache signed VPs across requests.
What’s next
Section titled “What’s next”You’ve seen all three flows. To dig into the cryptographic detail:
- Specification — the normative cryptosuite definition with byte-level mechanics
- Integration Guide — how to wire walt.id (or any verifier) to support this