OpenID4VP begrijpen: het protocol achter de EU Digital Identity Wallets

Een technische verdieping in OpenID4VP — het verificatieprotocol dat EUDI Wallets gebruiken om geverifieerde attributen te delen met bedrijven. Hoe het werkt, waarom het ertoe doet, en hoe OpenEUDI het implementeert.

eIDAS Pro Team
22 maart 2026
12 min leestijd

Gecorrigeerd op 20 oktober 2026: een eerdere versie van dit artikel stelde dat vragen om age_over_18 de geboortedatum niet onthult, alsof de EUDI-PID dat attribuut bevat. Dat doet hij niet: uit een PID ontvangt de verifier de geboortedatum en leidt hij zelf het ja/nee af; een age_over_18 zonder geboortedatum bestaat alleen in de aparte leeftijdsattestatie. Ook de codevoorbeelden zijn bijgewerkt naar de echte @openeudi-API. Verder hebben we gecorrigeerd hoe een verzoek aangeeft wat het wil: OpenID4VP 1.0 gebruikt DCQL (dcql_query); Presentation Exchange 2.0 hoorde alleen bij eerdere drafts. En we hebben de bewering geschrapt dat OpenEUDI een compliance-mapping per land meelevert — dat doet het niet. Ook hebben we verduidelijkt dat de wallet, en niet de SDK van de verifier, het WRPAC controleert.

Wat is OpenID4VP?

OpenID for Verifiable Presentations (OpenID4VP) is het protocol waarmee EU Digital Identity Wallets geverifieerde identiteitsattributen delen met bedrijven (Relying Parties genoemd). Het vormt de technische ruggengraat van de eIDAS 2.0-identiteitsverificatiestroom.

Wanneer een gebruiker bij het afrekenen een QR-code scant en zijn wallet het ondertekende gegeven terugstuurt waarmee de shop de vraag "is deze persoon ouder dan 18?" beantwoordt, verloopt die uitwisseling via OpenID4VP.

De verificatiestroom

De OpenID4VP-stroom kent vier actoren:

  1. Issuer -- de overheid of vertrouwde autoriteit die het credential heeft uitgegeven (bijv. de Oostenrijkse overheid die een nationale ID uitgeeft)
  2. Holder -- de burger met de EUDI Wallet op zijn telefoon
  3. Verifier (Relying Party) -- het bedrijf dat de verificatie aanvraagt (uw webshop, KYC-systeem, enz.)
  4. Trust Framework -- de EU Trusted Lists die vastleggen welke issuers en verifiers legitiem zijn

Stap voor stap

1. Verifier stelt een Authorization Request op
   ↓
2. De aanvraag wordt gecodeerd in een QR-code (of deep link)
   ↓
3. Holder scant de QR-code met de EUDI Wallet
   ↓
4. De Wallet verwerkt de aanvraag en toont wat er wordt gevraagd
   ↓
5. Holder controleert en keurt goed met biometrie/pincode
   ↓
6. De Wallet maakt een Verifiable Presentation (VP) aan
   ↓
7. De VP wordt verzonden naar het response-endpoint van de Verifier
   ↓
8. Verifier valideert de VP cryptografisch
   ↓
9. Geverifieerde attributen worden geëxtraheerd en gebruikt

Twee credentialformaten

EUDI Wallets ondersteunen twee credentialformaten, elk met een eigen cryptografisch verificatiepad:

SD-JWT VC (Selective Disclosure JWT)

Gebruikt voor remote verificatiestromen (online, cross-device). Het credential is een JSON Web Token met mogelijkheden voor selectieve openbaarmaking.

  • Format: JSON-gebaseerd
  • Selective Disclosure: hash-gebaseerd salten -- de issuer ondertekent alle claims, maar de holder kan er alleen specifieke onthullen
  • Signature: JWS (JSON Web Signature) met ES256
  • Verification: JWT parsen → disclosures reconstrueren → hashes verifiëren → signature valideren → trust chain van de issuer controleren
Header.Payload.Signature~Disclosure1~Disclosure2~KeyBindingJWT

mdoc / mDL (Mobile Document)

Gebruikt voor proximity-verificatiestromen (in persoon, NFC, BLE). Gebaseerd op ISO 18013-5.

  • Format: CBOR-gebaseerd (binair)
  • Selective Disclosure: ingebouwd in de datastructuur -- namespaces en dataelementen
  • Signature: COSE (CBOR Object Signing and Encryption)
  • Verification: CBOR decoderen → COSE-signature verifiëren → apparaatauthenticatie valideren → certificaatketen van de issuer controleren

Beide formaten bereiken hetzelfde doel -- cryptografisch bewijzen dat een vertrouwde issuer specifieke claims over de holder heeft geattesteerd -- maar via fundamenteel verschillende technische paden.

Attributen opvragen: DCQL

Het Authorization Request vertelt de wallet precies welke credential en welke claims de verifier wil. OpenID4VP 1.0 drukt dat uit met de Digital Credentials Query Language (DCQL), meegestuurd in de parameter dcql_query. (Eerdere drafts van OpenID4VP gebruikten in plaats daarvan DIF Presentation Exchange 2.0 en een presentation_definition-object; versie 1.0 heeft dat vervangen door DCQL.)

Een leeftijdscontrole tegen de PID in SD-JWT VC-formaat vraagt om één claim — de geboortedatum:

{ "credentials": [{ "id": "pid", "format": "dc+sd-jwt",
    "meta": { "vct_values": ["urn:eudi:pid:1"] },
    "claims": [{ "path": ["birthdate"] }] }] }

Hetzelfde verzoek tegen de mdoc-PID gebruikt de namespace eu.europa.ec.eudi.pid.1 en het element birth_date. DCQL kan ook alternatieven aanbieden: met credential_sets vraagt een verifier om "een leeftijdsattestatie of een PID", en de wallet geeft er één van terug.

WRPAC: het certificaat van de Verifier

Om een verificatieverzoek te doen, heeft de Relying Party een WRPAC (Wallet Relying Party Access Certificate) nodig. Dit certificaat, gedefinieerd in ETSI TS 119 475:

  • identificeert de Relying Party bij de wallet
  • specificeert welke attributen de RP mag opvragen
  • wordt uitgegeven door een gekwalificeerde vertrouwensdienstverlener
  • moet worden gevalideerd tegen de EU Trusted Lists

De wallet controleert het WRPAC voordat het toestemmingsscherm wordt getoond. Als het certificaat ongeldig, verlopen of niet in de trust list opgenomen is, weigert de wallet het verzoek.

EU Trusted Lists

De EU beheert een hiërarchische vertrouwensinfrastructuur:

EU List of Trusted Lists (LOTL)
  └── Country Trusted List (bijv. Luxembourg)
       └── Trust Service Provider (bijv. LuxTrust)
            └── Trust Service (bijv. WRPAC-uitgifte)
                 └── Certificate (uw WRPAC)

Het is de wallet die deze hiërarchie doorloopt om te controleren dat een WRPAC is uitgegeven door een legitieme aanbieder in een erkende lidstaat. Aan de kant van de verifier doet de SDK het spiegelbeeldige werk: die controleert dat het ontvangen bewijs is ondertekend door een uitgever waarvan het certificaat terugleidt naar een vertrouwensanker uit een gepubliceerde lijst (voor PID's de lijst van PID-aanbieders). Dit is een van de complexste onderdelen van de implementatie -- het trust-list-formaat (XML/JSON) heeft eigen parsingvereisten, en lijsten worden onafhankelijk bijgewerkt door elke lidstaat.

Privacy by design

OpenID4VP dwingt privacy af op protocolniveau:

  • Selective Disclosure: de holder kiest exact welke attributen worden gedeeld. Een leeftijdscontrole via de PID geeft alleen de geboortedatum vrij -- niet naam, adres of documentnummer -- en de verifier brengt die terug tot een ja/nee. Een age_over_18 zonder geboortedatum bestaat alleen in de aparte leeftijdsattestatie (proof-of-age attestation, eu.europa.ec.av.1), niet in de PID.
  • Geen correlatie: elke verificatiesessie gebruikt vers cryptografisch materiaal, waardoor verifiers sessies niet aan elkaar kunnen koppelen.
  • Toestemming van de holder: elk attribuutverzoek vereist expliciete goedkeuring van de gebruiker op het apparaat.
  • Dataminimalisatie: het protocol moedigt aan om alleen te vragen wat nodig is. De wallet-UI maakt overmatige verzoeken zichtbaar voor gebruikers.

Dit verschilt fundamenteel van traditionele KYC, waarbij bedrijven vaak meer data ontvangen (en opslaan) dan nodig is.

Hoe OpenEUDI dit implementeert

De OpenEUDI SDK is opgebouwd in lagen. Het pakket @openeudi/openid4vp implementeert credential-parsing met een eenvoudige API: parsePresentation(vpToken, options) detecteert automatisch het credentialformaat (SD-JWT VC of mDOC), valideert cryptografische signatures en geeft geverifieerde claims terug.

import { parsePresentation } from '@openeudi/openid4vp';

// Automatically handles both SD-JWT VC and mDOC formats
const result = await parsePresentation(vpToken, {
  nonce,               // the nonce from your Authorization Request
  trustedCertificates, // issuer trust anchors (DER), e.g. from EU Trusted Lists
});
// result.valid  — signature, key binding and trust checks passed
// result.format — 'sd-jwt-vc' | 'mdoc'
// result.claims — verified, selectively disclosed attributes (e.g. the PID birth date)

De hogerliggende VerificationService bouwt hierop voort om de volledige levenscyclus van een sessie te beheren:

import { VerificationService, VerificationType } from '@openeudi/core';
import type { IVerificationMode } from '@openeudi/core';

// @openeudi/core ships DemoMode and MockMode. Real wallets need your own
// IVerificationMode: it builds the OpenID4VP request (signed with your WRPAC)
// and verifies the response with @openeudi/openid4vp.
declare const productionMode: IVerificationMode;

const service = new VerificationService({ mode: productionMode });

const session = await service.createSession({ type: VerificationType.AGE });
// session.walletUrl — render as a QR code or deep link

service.on('session:verified', (session, result) => {
  // result.ageVerified — a derived boolean; the birth date is not part of the result
});

Intern doen de service en zijn verificatiemodus het volgende:

  1. Stellen een spec-conforme Authorization Request samen
  2. Coderen deze in een QR-code (generateQRCode uit @openeudi/core/qr)
  3. Nemen de VP van de wallet aan via service.handleCallback(), aangeroepen vanuit uw response-endpoint
  4. Delegeren aan @openeudi/openid4vp om de VP te parsen en te valideren (SD-JWT VC of mDOC)
  5. Controleren het certificaat van de issuer tegen de EU Trusted Lists
  6. Brengen de vrijgegeven claims terug tot het beleidsresultaat (ageVerified, countryVerified) -- een geboortedatum dient alleen voor de controle en wordt niet teruggegeven
  7. Laten sessies verlopen na hun TTL (standaard vijf minuten)

Dit alles gebeurt achter service.createSession(), service.handleCallback() en het event session:verified.

Grensoverschrijdende compliance

Elke EU-lidstaat heeft enige beleidsvrijheid over welke attributen onder welke voorwaarden mogen worden opgevraagd. Bijvoorbeeld:

  • Leeftijdsdrempels voor verificatie variëren per productcategorie en land
  • Sommige landen beperken bepaalde attribuutopenbaarmakingen
  • Nationale wallet-implementaties kunnen verschillende credentialschema's gebruiken

Controleer voordat u een verzoek opstelt wat de credential werkelijk bevat: in elke PID zijn birth_date en nationality verplicht, terwijl resident_country optioneel is en kan ontbreken.

Wat volgt voor het protocol

OpenID4VP 1.0 is een definitieve specificatie van de OpenID Foundation, en DCQL is de querytaal ervan — Presentation Exchange maakt er geen deel van uit. Het werk speelt zich nu af rond het kernprotocol:

  • Credentialstatus -- controleren of een credential is ingetrokken
  • Verbeteringen aan cross-device-stromen -- betere UX voor verificatie tussen mobiel en desktop
  • Alternatieven in één verzoek -- DCQL-credential_sets, bijvoorbeeld "leeftijdsattestatie of PID"

OpenEUDI levert protocolwijzigingen als nieuwe versies van zijn npm-pakketten.


OpenEUDI implementeert OpenID4VP voor het EUDI Wallet-ecosysteem. De SDK valt onder de Apache 2.0-licentie en gratis te gebruiken. Voor productie-implementaties met beheerde WRPAC-certificaten, zie de managed plannen van eIDAS Pro.

Gerelateerde artikelen

Dit artikel delen

Help anderen meer te weten komen over eIDAS-verificatie