EUDI Wallet age verification with Proof of Age
Use a Proof of Age credential when a service needs a threshold result such as “over 18” and does not need the person's date of birth. The credential issuer asserts the threshold; the relying party verifies the credential and uses the boolean result in its own age-gated flow.
In the DLBR SDK flow described here, Proof of Age is a separate mdoc credential profile. It is not an age claim inside EUDI PID, and this example does not cover every age-verification presentation method in the broader EU scheme.
Request only the age threshold
The EUDI Proof of Age mdoc profile uses document type and namespace eu.europa.ec.av.1. Its age_over_18 element is a boolean. The relying party must have an issuer and trust configuration that accepts this credential profile.
import { EidClient } from "@dlbr/eid-sdk";
const id = new EidClient({
baseUrl: process.env.DLBR_ID_BASE_URL!,
apiKey: process.env.DLBR_ID_API_KEY!,
mode: "test",
});
const session = await id.sessions.create({
credentials: [{
id: "proof-of-age",
format: "mso_mdoc",
issuer_id: "https://issuer.example",
trust_domain: "pub_eaa",
doc_type: "eu.europa.ec.av.1",
namespace: "eu.europa.ec.av.1",
claims: ["age_over_18"],
}],
});
// Display the public wallet URL as a link or QR code.
console.log(session.session_id, session.qr_code_url);Keep the API key and session handling on the server. The browser only needs the public wallet request URL and an opaque session identifier. Do not request the full PID or date of birth when the flow only needs the threshold.
Use the verified boolean result
Retrieve the session with the authenticated backend client. Treat pending, failed, and expired sessions as separate states; do not infer an age result from a missing credential or failed verification.
const result = await id.sessions.get(session.session_id);
if (result.status !== "VERIFIED") {
// Handle pending, failed, or expired verification separately.
return;
}
const credential = result.claims?.["proof-of-age"] as
| Record<string, unknown>
| undefined;
const namespace = credential?.["eu.europa.ec.av.1"] as
| Record<string, unknown>
| undefined;
const over18 = namespace?.age_over_18;
if (typeof over18 !== "boolean") {
throw new Error("Verified session did not contain the requested age result");
}
if (over18) {
// Continue with the age-gated action under your service policy.
} else {
// Decline the age-gated action or offer an appropriate alternative.
}The verified true value means the accepted issuer's attestation satisfied the requested threshold. It is not a date of birth, identity profile, or automatic authorization to provide the service.
Configure the issuer and relying party
Before production use, confirm all of the following with the environment operator:
- the issuer actually issues the
eu.europa.ec.av.1Proof of Age mdoc; - the relying-party policy permits that issuer,
pub_eaatrust domain, document type, andage_over_18claim; - the verifier has the trust material and validation configuration required for the issuer;
- the wallet and transport profile used by your users are supported by the integration.
The sandbox uses synthetic credentials to exercise the API and session flow. They are not credentials issued by an EUDI authority and do not demonstrate that a production issuer or wallet is supported.
Privacy and relying-party decisions
Request the minimum attribute needed for the stated purpose, explain the request to the user, and decide what result must be retained and for how long. The relying party remains responsible for applicable age-assurance rules, privacy notices, registration, fallback paths, and the final service decision. Wallet verification alone does not establish that an age-gated service meets all legal or safety requirements.
See EUDI Wallet data minimization and privacy for request-design and short-lived result-handling guidance.