Lion Cipher Lab
Present a verifiable credential with OpenID4VP (build the verifier and a wallet from scratch)
- Identity & Access Management
- Advanced
- ~90 min
- Free · no account required
Why it matters
A verifiable credential is only useful once someone PRESENTS it to a relying party -- a bar checking age, a bank checking residency, a website checking a licence. OpenID for Verifiable Presentations (OpenID4VP) is the protocol the wallet ecosystem (the EU Digital Identity Wallet among them) standardizes that exchange on. It looks like OAuth, but the roles are inverted: in plain OAuth an authorization server issues a token to a client; in OpenID4VP the WALLET presents a credential it already holds and the relying party is the VERIFIER. The verifier opens the exchange with an Authorization…
What you'll learn
- the OpenID4VP presentation flow end to end: Authorization Request -> wallet selection -> vp_token response -> verifier validation
- building a DIF Presentation Exchange `presentation_definition` that names exactly which claims a relying party may request
- writing the verifier (relying party) side: the ordered checks over issuer signature, _sd membership, requested-claims policy, and holder key binding
- why the Key-Binding JWT (aud + nonce + sd_hash, signed by the holder key in `cnf`) is the load-bearing control that stops a stolen credential being replayed
- the over-disclosure policy check -- rejecting a presentation that reveals MORE than the presentation_definition asked for -- which plain SD-JWT verification does not do
- naming and reproducing four OpenID4VP failure modes: cross-verifier replay (aud), nonce replay, missing/forged holder binding, and over-disclosure
- how OpenID4VP differs from plain OAuth 2.0: the wallet presents and the verifier is the relying party, not an authorization server issuing its own tokens
Steps in this lab
- The one idea: OpenID4VP is OAuth with the roles inverted
- Set up: a clean folder and two packages
- The toolkit and the issuer (oid4vp.py, part 1)
- The verifier's Authorization Request (presentation_definition + nonce + client_id)
- The wallet: match the definition, select disclosures, key-bind
- The verifier: the ordered checks, including the over-disclosure policy
- Run the happy path: reveal only age_over_18
- Break (a) -- replay to a DIFFERENT verifier: aud mismatch
- Break (b) -- reuse an old nonce: replay detected
- Break (c) -- no KB-JWT, or a KB-JWT the holder never signed
- Break (d) -- over-disclose beyond the presentation_definition
- Judgment: why holder binding is load-bearing, and OpenID4VP vs plain OAuth
- Clean up