Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Age Verification: OpenID4VP Fallback

This page describes the OpenID4VP fallback used for age verification when the W3C Digital Credentials API is unavailable, per the EU Age Verification Profile Annex A, Section A.5.

When to use the fallback

The W3C DC API is the primary method, but:

  • Browser support is still limited
  • It may be disabled by user preference or enterprise policy
  • On mobile, production wallets (as of mid-2026) reject the openid4vp protocol identifier inside the DC API handler

When the DC API is unavailable, the Relying Party falls back to OpenID4VP via av:// deep links or QR codes.

Flow

sequenceDiagram
    participant RP as Relying Party
    participant Wallet as Age Verification App

    RP->>RP: Generate nonce, build av:// URL with DCQL query
    RP->>Wallet: av://authorize?... (deep link or QR code)
    Wallet->>Wallet: Parse request, prompt user consent
    Wallet->>RP: POST direct_post (VP Token + state)
    RP->>RP: Validate nonce, signature, expiry
    RP-->>Wallet: redirect_uri

Key Requirements (per Annex A.5)

ParameterValueSource
URL schemeav://A.5 §Custom URL Scheme
response_typevp_tokenA.5 §Response Type
response_modedirect_postA.5 §Response Mode
client_id_schemeredirect_uriA.5 §Client Identifier Scheme
Request formatPlain query params (no JAR)A.5 §Request Signing (explicitly excluded)
Query formatDCQLA.5 §DCQL Query
nonceRequiredA.5 §Nonce Parameter

Explicitly out of scope

  • JAR (signed Authorization Requests) — not required
  • JWE encrypted responses (direct_post.jwt) — TLS is sufficient
  • Trust lists of RPs — no pre-registration
  • x509_san_dns / verifier_attestation — these depend on trust lists

Example Authorization Request

av://authorize?
  response_type=vp_token
  &response_mode=direct_post
  &client_id=redirect_uri%3Ahttps%3A%2F%2Frp.example.com%2Fcallback
  &response_uri=https%3A%2F%2Frp.example.com%2Fcallback
  &nonce=n-0S6_WzA2Mj
  &dcql_query=%7B%22credentials%22%3A%5B%7B%22id%22%3A%22proof_of_age%22%2C%22format%22%3A%22mso_mdoc%22%2C%22meta%22%3A%7B%22doctype_value%22%3A%22eu.europa.ec.av.1%22%7D%2C%22claims%22%3A%5B%7B%22path%22%3A%5B%22eu.europa.ec.av.1%22%2C%22age_over_18%22%5D%7D%5D%7D%5D%7D

Decoded dcql_query:

{
  "credentials": [{
    "id": "proof_of_age",
    "format": "mso_mdoc",
    "meta": { "doctype_value": "eu.europa.ec.av.1" },
    "claims": [{ "path": ["eu.europa.ec.av.1", "age_over_18"] }]
  }]
}

Handling the Response

The wallet POSTs to response_uri with application/x-www-form-urlencoded:

FieldDescription
vp_tokenBase64url-encoded DeviceResponse (mDoc CBOR)
stateClient-managed state (if provided)

The RP extracts claims from the vp_token, validates the nonce binding, and verifies the issuer signature against the trusted CA directory.

Security

  • Nonce: Generate a fresh, cryptographically random nonce per request. Store it server-side. Reject presentations with a missing or incorrect nonce.
  • HTTPS: response_uri must use HTTPS in production.
  • Data minimization: Request only age_over_18 — avoid name, address, portrait, or other identifying attributes.

Comparison with HAIP

FeatureAge Verification (Annex A)HAIP (EUDI Wallet)
Client ID schemeredirect_urix509_san_dns, x509_hash
Signed request (JAR)Not requiredRequired
Response modedirect_postdirect_post.jwt (JWE)
Trust modelTLS + Web PKIReader Trust Store + cert validation
Target walletAge Verification AppEUDI Wallet

For full details on HAIP, see the Protocols & Formats Summary and the Relying Party Requirements.

References