Protocols

eIDAS 2.0 — OID4VCI / OID4VP

Clavex is both a Credential Issuer (OID4VCI Final) and a Relying Party / Verifier (OID4VP 1.0 Final) for the European Digital Identity Wallet ecosystem. It issues SD-JWT VC and mso_mdoc (ISO 18013-5) credentials, and verifies presentations from any EUDI-compliant wallet using DCQL queries — tested against the official EUDI reference wallet.

Issuance Flow (OID4VCI)

Admin ─────> Clavex  POST /oid4vci/offers → credential_offer_uri + QR
User ─────> Wallet  scans QR / opens deep link
Wallet ─────> Clavex  POST /oid4vci/token (pre-authorized_code)
Clavex ─────> Wallet  access_token + c_nonce
Wallet ─────> Clavex  POST /oid4vci/credential (holder key proof JWT)
Clavex ─────> Wallet  SD-JWT VC or mso_mdoc credential

Step 1 — Configure a Credential Type

A credential config defines the Verifiable Credential Type (vct — a globally unique HTTPS URL), its display name, and how user/claims data maps into the issued credential.

bash
$ curl -s -X POST https://id.clavex.eu/api/v1/organizations/$ORG_ID/oid4vci/configs \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "vct": "https://id.clavex.eu/vct/employee-badge", "display_name": "Employee Badge", "category": "badge", "ttl_seconds": 2592000, "claims_mapping": {"employee_id": "metadata.employee_id", "department": "metadata.department"} }' | jq '{id,vct,display_name}' { "id": "01925f3a-...", "vct": "https://id.clavex.eu/vct/employee-badge", "display_name": "Employee Badge" }
Shortcut: for common identity credentials (SPID/CIE-sourced PID, mDL) Clavex ships one-click identity presets so you don't have to hand-write the config — see the credential config admin UI.

Step 2 — Issue a Test Credential

An offer is a single-use, pre-authorized issuance grant. Binding it to a user_id issues real user claims; omitting it issues from a custom payload instead (useful for testing).

bash
$ curl -s -X POST https://id.clavex.eu/api/v1/organizations/$ORG_ID/oid4vci/offers \ -H "Authorization: Bearer $ORG_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "vct": "https://id.clavex.eu/vct/employee-badge", "ttl_minutes": 15, "payload": {"employee_id": "E-1042", "department": "Engineering"} }' | jq '{offer_id,credential_offer_uri}' { "offer_id": "a1b2c3...", "credential_offer_uri": "openid-credential-offer://?credential_offer_uri=https://id.clavex.eu/acme/oid4vci/offers/a1b2c3..." }
# Or fetch a scannable QR code directly: GET https://id.clavex.eu/api/v1/organizations/$ORG_ID/oid4vci/offers/a1b2c3.../qr

Scan the QR with any OID4VCI-compliant wallet (e.g. the EUDI reference wallet) to walk through the token exchange and credential request automatically. For debugging, the underlying wallet calls look like this:

bash
# 1. Wallet exchanges the pre-authorized_code for an access token $ curl -s -X POST https://id.clavex.eu/acme/oid4vci/token \ -d "grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code" \ -d "pre-authorized_code=$PRE_AUTH_CODE" | jq '{access_token,c_nonce}'
# 2. Wallet requests the credential, proving key possession via a proof JWT $ curl -s -X POST https://id.clavex.eu/acme/oid4vci/credential \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "credential_configuration_id": "EmployeeBadge", "proof": {"proof_type": "jwt", "jwt": "$PROOF_JWT"} }' | jq .

Step 3 — Verify a Presentation (OID4VP)

Use DCQL (Digital Credentials Query Language, OID4VP 1.0 Final §6) to describe what you need. Clavex returns a signed authorization request (JAR JWT, x509_san_dns scheme) that the wallet fetches and answers.

bash
$ curl -s -X POST https://id.clavex.eu/acme/wallet/request \ -H "Content-Type: application/json" \ -d '{ "dcql_query": { "credentials": [{ "id": "badge", "format": "dc+sd-jwt", "meta": {"vct_values": ["https://id.clavex.eu/vct/employee-badge"]}, "claims": [{"path": ["department"]}] }] } }' | jq '{request_id,request_uri}'
# QR code for the same request: GET https://id.clavex.eu/acme/wallet/request/{request_id}/qr
# Poll for completion (used by browser-based login challenges): GET https://id.clavex.eu/acme/wallet/request/{request_id}/status

Once the wallet POSTs its vp_token to /acme/wallet/response, Clavex verifies the SD-JWT signature, the key-binding JWT (nonce + audience), and issuer trust (against a configured allowlist, unless credentials are self-issued), then returns redirect_uri plus any chained_offers if this credential is configured to unlock further issuance ("credential chaining" — e.g. a verified PID unlocking an employee badge issuance).

mso_mdoc / ISO 18013-5

Alongside SD-JWT VC, Clavex issues and verifies the mso_mdoc format used by mobile driving licenses and the EUDI reference wallet's proximity (BLE/NFC) flows: MSO signature and digest verification, DeviceResponse CBOR parsing, and DCQL queries with "format": "mso_mdoc" are all implemented and exercised against the official EUDI reference wallet.

Where this is simplified here: proximity presentation (BLE device engagement, ISO 18013-5 session transcripts) and HAIP-specific trust-anchor setup are out of scope for this quickstart — they involve device-level engagement outside the HTTP request/response flows shown above. See the full walkthrough on the Clavex blog: mdoc Proximity End-to-End with the EUDIW Reference Wallet and EUDIW Reference Wallet with a Self-Hosted IAM.

Relying Party registration: to register Clavex as a Relying Party at a national Trust Anchor (e.g. AgID), fetch a signed OpenID Federation entity configuration via GET /api/v1/organizations/{org}/eidas-rp-metadata?purpose=....