Verified data for applications (verified_claims)¶
An application can receive what the user's EU Digital Identity Wallet verified in the current sign-in — PID claims, derived age thresholds such as age_over_18, and the claims of other credentials — the standard OIDC way, as OpenID Connect for Identity Assurance verified_claims. The data can come from a wallet sign-in or a verify-only step-up. Nothing is released unless an administrator opts the application in. (Preview, not yet tested with a real national wallet.)
Status: Preview — not yet tested with a real national wallet
New in 1.1.0-rc.1. Tested with the project's own test wallet.
Per-client policy¶
Console: Applications → the application → Verified data (wallet) (the application needs an OIDC client). Terraform: helix_client_verified_claims.
| Field | Meaning |
|---|---|
enabled |
the opt-in. No policy, or enabled: false, releases nothing |
allowedClaims |
1–100 entries: PID claims (given_name, family_name, birthdate, nationalities, place_of_birth, address, their dotted members, issuing_country, issuing_authority), age_over_NN (1–150, derived from the verified birth date, which is not released unless listed), a credential id (qualification: all its claims) or one of its claims (qualification.profession). An entry allows everything below it. The administrative number is never releasable |
deliverUserinfo |
default on: the userinfo response |
deliverIdToken |
default off: the ID token of the code exchange only (never a refreshed one) |
deliverAccessToken |
default off: every access token of the authorization (code and refresh). Every API the token reaches can read them; never a BSN |
maxAgeSeconds |
optional, 60–2592000: how long after the wallet verification the claims may still be released (lifetime) |
apiVerification, apiPredicateClaims |
for the verification API only |
useStoredAttributes |
default off: when the sign-in has no wallet evidence, release from the user's verified attributes kept on the profile (marked "verification_process": "helix:stored"). Terraform use_stored_attributes |
curl -s -b cookies.txt -c cookies.txt "$HELIX_URL/admin/realms/$REALM/verified-claims-catalog"
CSRF=$(awk '($6=="__Host-XSRF-TOKEN"||$6=="XSRF-TOKEN")&&$7~/^r1\./{t=$7} END{print t}' cookies.txt)
curl -s -b cookies.txt -X PUT -H "Content-Type: application/json" -H "X-XSRF-TOKEN: $CSRF" \
-d '{"enabled": true,
"allowedClaims": ["given_name", "age_over_18", "qualification.profession"],
"deliverUserinfo": true, "maxAgeSeconds": 900}' \
"$HELIX_URL/admin/realms/$REALM/verified-claims/citizen-portal"
The catalogue (GET …/verified-claims-catalog) lists what the realm's wallet connections verify: the PID claims, each connection's other credentials and their claims. GET …/verified-claims/{clientId} reads a policy (view-clients), GET …/verified-claims lists them, and DELETE …/verified-claims/{clientId} stops the release (console: Remove policy, confirmed and audited; the dialog names the application).
Who may write a policy. Writing one needs realm-admin: releasing wallet-verified personal data is a privacy decision on the level of the BSN release. It also passes the client-change guards: the caller must hold the client's service-account privileges, a client with a BSN grant needs realm-admin for any change, and the built-in helix-console / helix-cli clients never get a policy. Deleting a client (or its application) deletes its policy, so a client re-created under the same client_id inherits nothing. Policy changes are audited as VERIFIED_CLAIMS_POLICY_UPDATED / VERIFIED_CLAIMS_POLICY_DELETED.
Channels and how an application asks¶
- Scope
verified_claims(seeded in every realm; register it on the client): everything the policy allows, in every channel the policy enables. - The
claimsparameter (OIDC Core §5.5), foruserinfoand/orid_token, wins over the scope for that channel. Released is what the policy allows and the request names:
{"userinfo": {"verified_claims": {
"verification": {"trust_framework": null},
"claims": {"given_name": null,
"age_over_18": {"essential": true, "purpose": "To check you may buy this"},
"qualification": {"profession": {"value": "doctor"}}}}}}
value/valuesrelease a claim only when the value that would be released matches (the boolean of an age threshold, a member of a list). A mismatch looks exactly like a claim that is not available, so a birth date cannot be probed throughage_over_18.- A request whose
trust_frameworkexcludeseidasgets nothing. - A malformed
claimsparameter, an emptyclaimsobject, apurposeoutside 3–300 characters, or more than 16 KiB, 100 claims or 20 values is refused withinvalid_request. - Essential claims: when an essential claim the policy allows is not in the session's wallet verification and the application's login flow has the wallet verification step, the user signs in again once for that request and confirms with the wallet. Otherwise (also with
prompt=none, for an impersonation, a PAR request, or a claim requested only in a channel the policy does not deliver) it is omitted.
What the application receives¶
"verified_claims": [
{"verification": {"trust_framework": "eidas", "assurance_level": "high",
"time": "2026-10-19T09:12:44Z",
"evidence": [{"type": "electronic_record", "check_details": [{"check_method": "vcrypt"}],
"time": "2026-10-19T09:12:44Z",
"record": {"type": "eudi_wallet_pid", "source": {"name": "CN=PID Issuer,C=DE"}}}]},
"claims": {"given_name": "Erika", "age_over_18": true}},
{"verification": {"trust_framework": "eidas", "time": "2026-10-19T09:12:44Z",
"evidence": [{"type": "electronic_record", "check_details": [{"check_method": "vcrypt"}],
"time": "2026-10-19T09:12:44Z", "record": {"type": "eudi_wallet_eaa"}}]},
"claims": {"qualification": {"profession": "doctor"}}}
]
One element per credential; a credential's claims sit under its id. assurance_level is the IDA value for the eidas trust framework (low, substantial, high). Dotted PID names are nested (address.country → "address": {"country": …}), and nationalities is a list. Read it from userinfo with the access token:
Lifetime¶
Only the current session's verification is released, and only until that session ends:
maxAgeSecondslimits how long after the wallet verification the claims may be released. Unset: the realm's SSO session maximum lifetime, else 10 hours.- Independently, nothing is released once the SSO session that verified it has ended: logout, revocation, idle or maximum-lifetime expiry, or a new sign-in in that browser. A later wallet step-up replaces the verification.
- Every delivery applies the application's current policy: removing a claim or a channel takes effect at the next userinfo call or token refresh; a removed or disabled policy releases nothing.
- The verification waits in the session and on the authorization, sealed with AES-GCM under a key derived from
DB_ENCRYPTIONand bound to its realm, client, authorization and channel. It is never written to the account. The stored authorization keeps noverified_claimsin its token claims, so token introspection does not return them; the signed token values the application holds still carry them. - Only for the authorization-code grant or its refresh, only to the client the authorization belongs to, never for an impersonation. Token exchange (an agent acting for the user), client credentials, device, CIBA and every other grant get none.
BSN rules¶
A released claim counts as a BSN when its name contains bsn, burgerservicenummer, citizen_service_number or sofi or names a national person identifier, or when a value contains the DigiD sector form, an eIDAS NL/xx/<bsn>, or any 8- or 9-digit sequence (per token, raw, and without spaces, dots or hyphens) that passes the 11-proef — only a valid ISO date is skipped.
Such a claim goes only to userinfo, only while the client has a BSN grant (the application's BSN release setting), never into a token. The check fails closed: long numbers such as IBANs or phone numbers may be withheld from tokens.
Discovery¶
The realm's OpenID configuration advertises claims_parameter_supported, verified_claims_supported, trust_frameworks_supported (eidas), evidence_supported (electronic_record), electronic_records_supported, claims_in_verified_claims_supported (the PID claims and age_over_18), acr_values_supported (the eIDAS levels; high is the wallet and wallet step-up), and acr / verified_claims in claims_supported:
curl -s "$HELIX_URL/realms/$REALM/.well-known/openid-configuration" \
| jq '{verified_claims_supported, trust_frameworks_supported, claims_in_verified_claims_supported}'
Changed for verify-only connections¶
A client with an enabled policy no longer gets the verify-only connection's verify.releasedClaims ID-token release; its policy governs, in userinfo by default. Clients without a policy are unchanged, and the verify-only release now also uses the IDA value high as assurance_level instead of the LoA URI.
Audit¶
| Event | When | Detail |
|---|---|---|
VERIFIED_CLAIMS_RELEASED (actor: the user) |
each delivery: the ID token, an access token, a userinfo response | clientId, channel, claims (names), credentials, legalBasis (when a BSN was released) — never a value |
VERIFIED_CLAIMS_POLICY_UPDATED / _DELETED |
a policy change | clientId, enabled, allowedClaims, channels |
Not in this release¶
Showing purpose on the consent page; the essential-claim step-up for PAR requests; requesting individual verification / evidence sub-elements; verified claims over SAML; listing a realm's credential claims in discovery; verified-claims policies in realm export / import.
See also¶
- Verification API — verify attributes without a sign-in
- Credentials beyond the PID · EU Digital Identity Wallet
- Client scopes & claims