Bridge OIDC - EUDI Wallet
Documentation version: 1.9.1 · Last updated: 2 November 2026
Add identity verification or age verification (18+) with an EUDI Wallet to your application using OpenID Connect. The bridge handles the wallet interaction and credential verification; your application receives a signed OIDC result and manages its own accounts, permissions and session.
Use this guide with WordPress, Moodle, Keycloak, a reverse proxy, a CMS, or any compatible server-side OIDC client. Start by registering a client and configuring Discovery. Then follow the recipe for your software. No Wallet protocol implementation is needed in your application.
Audience: developers integrating the hosted Wallet Connectors service into their own applications. You need access to your application configuration and callback handler; no access to our servers, databases or administration API is required. The Shopify source-installation guide separately covers hosting your own connector backend, not operating the hosted bridge.
| Your integration | Start here |
|---|---|
| WordPress / WooCommerce | WordPress setup |
| Keycloak as an identity broker | Keycloak setup |
| Protect an existing website with a proxy | OAuth2 Proxy setup |
| Nextcloud | Nextcloud setup |
| Moodle — Wallet sign-in and learning attestations | Moodle plugins and setup |
| Other CMS, identity software or custom application | Common OIDC settings and OIDC client libraries |
| Try an AI assistant with wallet age verification | Talao Events demonstration |
| AI agents — Claude, Codex and other MCP clients | MCP age verification |
| Shopify — age verification at checkout | Shopify setup and demonstration |
| Check age without identifying a user | Age verification |
| Use France Identité Partenaires | France Identité compatibility |
| Understand data retention | Privacy and data retention |
| Inspect protocol test evidence and its limits | Conformance testing |
Dedicated plugins are available: Talao Wallet Login for EUDI Wallet for WordPress / WooCommerce, and EUDI Wallet Login for Moodle. Download and installation instructions are included below. Moodle provides identity sign-in through the bridge and a separate plugin for delivering learning attestations directly through the issuance API; the WordPress plugin also offers an age-verification mode. Other software recipes are based on publisher documentation. Validate the complete Wallet login in your own environment before opening access to users.
For AI agents, start with the GPT event assistant demonstration to understand the user journey, or install the MCP connector in your own AI application. Register a public MCP age client, download its JSON and configure Claude, Codex or another local stdio client. No Application API key is required. A remote MCP service for cloud-hosted assistants requires a separate deployment.
Try the examples before integrating
| Example website | What to try | Setup instructions |
|---|---|---|
| WordPress demonstration · English | Present an age attestation and see the plugin's success confirmation. This public explanation site keeps its content accessible without proof. | WordPress plugin |
| Moodle demonstration | Sign in as a student with an identity wallet, follow a course and retrieve an eligible awarded badge as a wallet attestation. | Moodle's two plugins |
| Shopify demonstration | Compare a restricted wine product with an unrestricted grape juice, then verify your age before test checkout. Visitor password: demo-eudi. |
Shopify setup |
| Talao Events — French · English | Ask GPT about an adult event, prove your age and unlock a fictional backstage offer through MCP. | MCP connector and worked example |
Visitors use the clients already configured on these sites. Register your own client when integrating the connector into your application. Moodle issuance uses separate API credentials, configured by the institution rather than students.
1 Register your application
Create a free application client. The form works for an application, CMS, plugin, identity broker such as Keycloak, or other OIDC software.
- Enter the company name, the contact's first and last name, and the developer contact email. This address is used to manage the client; it is separate from the attributes of people who will sign in.
- Enter a public application name and the exact HTTPS callback URL
(
redirect_uri) supplied by your software, including its path and any query string. The callback must process OIDC responses. A website homepage is suitable only if it actually implements that callback. - Choose Identity verification or Age verification (18+). For identity verification, you may also authorize a separately verified user contact email. An age client receives no name, birth date or user email. Optionally expand Customize the verification page to add a display name, application colour and PNG/JPEG logo (maximum 64 KB, dimensions up to 1024 × 1024). Leave these fields unchanged to keep the standard page. The registered callback hostname remains visible; application branding is not a certification. An administrator can update or disable it later. These options apply to identity and age verification and do not change the configuration JSON imported by WordPress or Moodle; existing plugin versions remain compatible.
- Confirm that you have read the linked terms of use and privacy policy, then request the email code.
- Check the application/service/callback summary and enter the six-digit code sent to the developer contact. It expires after five minutes, permits five attempts, and can be resent after 60 seconds. You can correct the contact details before completing registration.
- After successful email verification, the service creates the client and
generates its
client_idand secret. Copy the issuer, Client ID and secret into your application. The secret appears only in this response, for at most five minutes with JavaScript enabled. Close removes it from the page; the client remains active. Reopening the form does not reveal the secret.
You can also select Download configuration (JSON) before the five-minute display expires. The file contains the issuer, Discovery URL, Client ID, client secret, callback, scopes, token authentication method, S256 setting and identity/age purpose. Use these values in your application's settings; in the WordPress plugin Talao Wallet Login for EUDI Wallet, select Choose the configuration file (JSON) to load the file. Other software may require manual entry. Keep the file private. Closing the page or expiration of the display does not delete a downloaded file. The browser generates it from this one-time response; there is no later secret-download endpoint.
Company/contact details and the record of document acknowledgement are available only to the service's authenticated administrators. They are not sign-in claims. No client is created while the email challenge is pending. Registration does not create or link accounts inside your application.
A free client uses Authorization Code, client_secret_basic, PKCE S256 and one
registered HTTPS callback. An identity client requests openid profile for first
and last name, optionally email for email / email_verified. An age client
requests openid age_verification for age_over_18=true. Use separate clients
for identity and age verification, staging and production.
| Free-service limit | Default |
|---|---|
| Login starts | 100/month and 10 per UTC day, shared by free clients with the same callback hostname |
| Concurrent logins | 3 per callback hostname |
| Client registrations | 3 per UTC day for each callback hostname, source IP and verified developer email |
| Email code | 5 minutes, 5 attempts, 60-second resend cooldown |
The Pro offer at 39 EUR/month excluding VAT includes 3,000 verification attempts per UTC calendar month and 300 attempts per UTC day, 30 concurrent sign-ins, 120 starts/minute/client and 60 starts/minute/IP/client, with dedicated client budgets. It includes 3,000 verification attempts per UTC calendar month, without carryover. Custom allowances are agreed separately. Moving an existing client to Pro/Custom preserves its credentials and plugin configuration; ask the support rather than creating a replacement client. Quotas are enforced by the provider, not stored as authoritative values in the configuration JSON.
The form displays the service's configured limits. Failed and abandoned login starts count. Daily allowances reset at midnight UTC. Reaching a limit does not trigger a charge; wait for capacity or the next day. Changing a callback hostname can also change user identifiers; plan this before moving an application.
Registration validates callback syntax and restrictions, without probing the
callback over HTTP. A working callback may reject a request without code and
state, while an ordinary page may return HTTP 200 without supporting OIDC.
Validate it through a complete login.
For lost secrets, additional callbacks or a different client authentication method, contact us. PKCE S256 is required for every client, including confidential clients. The form is an interactive website, not a Dynamic Client Registration API. Discovery does not create a client.
2 Configure using discovery
Issuer:
https://login.openid4vc-hub.com
Discovery document:
https://login.openid4vc-hub.com/.well-known/openid-configuration
Configure an OIDC client library with the issuer (or its discovery URL), your client ID, registered callback and, for a confidential client, its secret. Enable Authorization Code and PKCE S256. Use discovery to locate authorize, token, UserInfo and JWKS instead of hardcoding those endpoints. Require the metadata issuer to equal the configured issuer exactly and retrieve it over verified HTTPS.
The document advertises:
{
"issuer": "https://login.openid4vc-hub.com",
"authorization_endpoint": "https://login.openid4vc-hub.com/authorize",
"token_endpoint": "https://login.openid4vc-hub.com/token",
"userinfo_endpoint": "https://login.openid4vc-hub.com/userinfo",
"jwks_uri": "https://login.openid4vc-hub.com/jwks",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"response_modes_supported": ["query", "form_post"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"]
}
Use the actual document, not this illustrative subset. registration_endpoint,
refresh-token and logout endpoints are intentionally absent. Discovery and JWKS
are public; no verification service key or client secret is required to read them. For local
deployment, substitute the separately configured local HTTPS issuer everywhere.
3 Start an authorization
Create new random values for each login:
stateto bind the callback to the initiating browser transaction;nonceto bind the ID Token to that authentication request;code_verifier, a high-entropy string of 43–128 characters fromA–Z a–z 0–9 - . _ ~;code_challenge = BASE64URL(SHA256(ASCII(code_verifier))), without padding.
Keep state, nonce and code_verifier in your application's server-side transaction store, with an expiry and browser binding. Do not put the verifier in the authorization URL. Do not reuse it across tabs or login attempts.
Example authorization parameters (URL-encode all values):
GET <authorization_endpoint>?
response_type=code&
client_id=<registered-client-id>&
redirect_uri=https%3A%2F%2Fapplication.example%2Foidc%2Fcallback&
scope=openid%20profile&
state=<random-state>&
nonce=<random-nonce>&
code_challenge=<S256-challenge>&
code_challenge_method=S256&
response_mode=query
redirect_uri is required and must match an entry exactly. openid is
required. profile and optional email are allowed only if registered for your client.
The provider advertises email only when email delivery is enabled.
query is the default response mode; form_post returns an auto-submitted HTML
form to your callback instead. Handle SameSite cookies correctly if choosing
form_post. Use HTTPS for callbacks in production and phone/same-device tests.
A separately configured loopback-only development installation can use HTTP
callbacks for desktop development; see README.md. That mode is never used for
the public issuer.
The bridge displays the application, requests the identity credential via the verification service, and returns the browser automatically after a successful verification. The Wallet authorizes the presentation; there is no second consent screen on the bridge. A Return to application button is available if automatic return fails; it does not request additional consent. If a login has already completed, start a new authorization request rather than resubmitting it or retrying a used code. Mobile return requires the browser that initiated the login. Desktop browsers automatically start a cross-device QR presentation; smartphones start a same-device presentation and display an explicit Wallet deeplink button. The choice uses browser hints and does not replace the verification checks. If you need same-device-only presentation, contact us to discuss the configuration.
There is no persistent bridge SSO session. prompt=none fails with
login_required; a fresh login uses a new Wallet presentation. The implementation
accepts prompt=login, consent or select_account; account selection is made
in the Wallet. It validates nonnegative max_age and performs a fresh
presentation. Unsupported claims/request-object parameters are rejected rather
than forwarded to the verification service.
4 Process the callback and exchange the code
The successful callback contains code and the original state. Before calling
the token endpoint, validate state, browser binding, expiry and one-time use.
Handle error callbacks too, always checking their state. A callback alone does
not establish a session.
Exchange the code promptly (60-second lifetime) at the discovered token endpoint:
POST <token_endpoint>
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
grant_type=authorization_code&code=<code>&redirect_uri=<exact-registered-uri>&code_verifier=<original-verifier>
For client_secret_post, omit Basic authentication and include client_id and
client_secret in the form. For a public client, include client_id and no secret.
PKCE remains required in all cases. Do not mix authentication methods. The token
endpoint accepts form-encoded POST bodies, not query parameters or JSON.
Success returns an RS256-signed id_token, an opaque Bearer access_token,
expires_in=300 and the granted scope. There is no refresh token. Codes are
one-time: a lost token response requires a new login, not repeated redemption.
Use an OIDC library to verify the ID Token against the discovered JWKS. Validate
its signature and permitted algorithm, iss, aud, expiry and the exact nonce.
For multiple audiences, validate azp as required by OIDC. Never accept alg=none,
an unverified decoded JWT or an audience belonging to another application.
The bridge returns an at_hash binding the access token to the ID Token.
Then establish your application's own session and rotate its session identifier. The bridge does not manage the application's accounts, roles or permissions.
5 UserInfo and attributes
GET <userinfo_endpoint>
Authorization: Bearer <access_token>
POST is also supported with the same Authorization header. Do not place access
tokens in a URL. Check that UserInfo sub equals the ID Token's sub.
The current endpoints are intended for server-side HTTP exchanges; browser SPA cross-origin Token/UserInfo requests are not enabled by CORS. Public clients can use a server callback/BFF without a client secret. A direct browser-only SPA needs a separately reviewed CORS integration.
| Scope | Possible authorized claims |
|---|---|
openid |
Pairwise sub, with standard ID Token protocol claims |
profile |
given_name, family_name, birthdate, place_of_birth, restricted by client policy |
email |
email, email_verified: true, separately verified by the bridge and authorized for this client |
place_of_birth is a bridge profile extension, not an OIDC Core standard claim.
With the Rulebook policy, it is a JSON object containing at least one of country, region or locality,
for example {"locality": "Paris", "country": "FR"}. It is included only when
profile is requested and the client is authorized for this claim. The same
projection is applied to the ID Token and UserInfo.
Email is optional and is verified separately by the bridge, never certified by France Identité or obtained from its identity credential. Nationality is neither requested nor used by the bridge. The verification service verifies the four configured identity attributes, independently of the subset shared with your application. No raw identity credential, CBOR, JWT credential, Wallet proof or key is returned.
5.1 Optional email verification
Register the email scope and both email claims, then request
scope=openid profile email. After Identity verification, the bridge asks for a contact
address and a six-digit email code unless a contact for this application sector
was verified within 30 days. The code is entered in the original browser, not
returned to your application. The ID Token and UserInfo include the same verified
email. email_verified: true means the bridge verified control of the mailbox;
it does not assert that the identity credential provider certified this address or that it is
unique to this legal person.
Use prompt=consent with the email scope to show the saved contact and offer a
change verified by a new code. Email never changes sub. Do not link or merge
accounts automatically by email. If email delivery is unavailable or the client
is not authorized, handle the OIDC error; never invent a fallback address.
An email-flow login may take up to 15 minutes including the identity verification stage, so retain
your state/nonce/PKCE transaction long enough (900 seconds) without changing the
short authorization-code lifetime. Cancellation still returns an OIDC error.
The bridge's registration, PKCE and signature-validation requirements are unchanged.
6 User identifiers and existing accounts
Store the identity key as (iss, sub). The sub is an opaque stored identifier
for the subject and the application's sector. The sector is the host
of the registered callback URLs: different sectors receive different subs;
clients sharing a sector can share the same sub. Changing an application's
callback hostname can change its sub and needs a planned account migration.
The service matches verified given name, family name, birth date and birthplace. Its identity registry uses pseudonymous references rather than raw identity attributes. The pairwise sub is random and persisted; your application must treat it as opaque and must not derive it from claims.
This policy has an explicit limit: identical four-field identities are merged, and a correction, name change or different birthplace representation can produce another subject. Credential renewal preserves sub only when these inputs and the sector remain the same. It is not a guaranteed unique legal-person identifier. Do not automatically link existing accounts by email, name, date of birth or a newly received sub.
The application owns account linking: first authenticate the existing local
account, require an explicit recent authorization to link, then complete a fresh
OIDC login and store (iss, sub). Prevent linking an already attached identity to
another account.
7 Verify age without identifying a user
Choose Age verification (18+) in the
client registration form.
Use a dedicated client and the same OIDC endpoints and validation steps as for
Identity verification, with scope=openid age_verification and mandatory PKCE S256.
Do not request profile or email for this client.
The Wallet presents a Proof of Age mdoc (eu.europa.ec.av.1) disclosing only the
boolean age_over_18. The bridge accepts only true after the verification service has verified
the credential, holder binding, validity and established issuer trust. The
presentation uses the dedicated CORE flow with an unsigned request, av://
and direct_post; it is not a HAIP presentation. This follows the request shape
shown in the Age Verification profile,
without claiming exhaustive implementation of that profile.
A successful ID Token and UserInfo include age_over_18: true. The OIDC sub
is a random identifier for this verification, changes on the next verification,
and is not stored as a persistent bridge identity. Do not use this subject to
create, link or recover an account. No identity credential, name, birth date, birthplace or email
is returned. A refusal, an invalid result or a missing/false age claim must not
grant access. The bridge does not infer age from a date of birth.
Your application controls the short-lived access permission after validating the OIDC response. The WordPress plugin retains a protected browser age session for five minutes, without creating or signing into a WordPress account. The plugin's age mode gates public WordPress pages and REST access. Exclude protected content from page/CDN caches: caches can serve a response before PHP runs. Public media URLs and files served directly by the web server require their own access rules. This feature alone does not establish regulatory compliance or prevent shared-device use.
8 Connect your software
For WordPress / WooCommerce and Moodle, install our dedicated plugins using the instructions below. They handle the OIDC connection to the bridge; no additional OIDC extension or identity broker is required.
For software without a dedicated connector, choose an OIDC client extension that signs users into your application through an external provider. Check support for Authorization Code, PKCE S256, signed ID Token validation and nonce validation before installing or purchasing it. An extension that turns the CMS into an OIDC provider serves a different purpose.
Choose your integration below. Each installation follows the same sequence: install the component → copy its callback → register a bridge client → configure the connection → activate it → test the complete journey. The MCP connector uses a fixed loopback callback; Moodle issuance uses a separate API key instead of an OIDC client.
| Software | What you can add | Component and instructions |
|---|---|---|
| WordPress / WooCommerce | Identity sign-in or site-wide 18+ access | Our WordPress plugin |
| Moodle | Student sign-in and optional learning-attestation delivery | Our two Moodle plugins |
| Shopify | Age verification for selected products at checkout | Our Shopify application |
| AI applications | Request an 18+ proof from an agent | Our local MCP connector |
| Keycloak | Add wallet identity sign-in to your realm | External OIDC identity provider |
| OAuth2 Proxy / Nextcloud / other CMS | Add identity sign-in using an OIDC client | Follow the software-specific section below |
| Custom application | Implement identity sign-in or age verification | OIDC libraries |
8.1 Settings shared by OIDC clients
First copy the callback URL generated by your software and open the
free client registration form.
Supply the exact URL, including path and query string, and verify the developer
contact email. Use a separate registration for staging and production. The free
form issues a confidential client using client_secret_basic; other client
types are available by contacting us.
| Application setting | Value |
|---|---|
| Provider / issuer | https://login.openid4vc-hub.com |
| Discovery / configuration URL | https://login.openid4vc-hub.com/.well-known/openid-configuration |
| Authorization endpoint, if entered manually | https://login.openid4vc-hub.com/authorize |
| Token endpoint, if entered manually | https://login.openid4vc-hub.com/token |
| UserInfo endpoint, if entered manually | https://login.openid4vc-hub.com/userinfo |
| JWKS URL | https://login.openid4vc-hub.com/jwks |
| Grant / response type | Authorization Code / code |
| PKCE | Enabled, method S256, including for confidential clients |
| Token authentication | The registered method: client_secret_basic, client_secret_post or none |
| Client ID / secret | Values supplied for this application; keep the secret server-side |
| Scopes — identity | openid profile; add email only when authorized and needed |
| Scopes — age verification | openid age_verification; use a separate age client |
| Signature algorithm | RS256, verified using JWKS |
| External account identifier | sub, stored together with the issuer |
| Response mode | query for the initial integration |
| Logout / refresh | Leave provider logout unset; do not request offline_access or require refresh tokens |
| Login transaction timeout | 300 seconds for identity only; 900 seconds if requesting email |
Prefer Discovery when the extension supports it. Keep HTTPS certificate, state,
nonce, issuer, audience and expiry validation enabled. A client secret does not
replace PKCE. The authorization code must still be exchanged within 60 seconds.
If your CMS requires an email to create accounts, request openid profile email
and have both email and email_verified authorized for the client. Email is
verified separately after the Wallet step; it is not an address certified by the identity provider.
8.2 Keycloak identity broker
Prerequisites
Administrator access to a Keycloak realm with external OIDC identity providers.
Installation and configuration
Use Identity Providers → Add provider → OpenID Connect v1.0 in your realm.
Choose an alias such as eudi-wallet and copy the displayed Redirect URI for
registration. It commonly ends in /realms/<realm>/broker/eudi-wallet/endpoint;
use the actual value, including any reverse-proxy path. Create an Identity
verification client using the registration form,
verify your developer email, and copy the issued Client ID and one-time secret.
Then enter those credentials in Keycloak.
Your applications remain clients of Keycloak; Keycloak is the client of the bridge.
| Keycloak provider setting | Value |
|---|---|
| Discovery / issuer | Bridge Discovery URL / issuer from section 2 |
| Client ID / secret | Credentials issued for this Keycloak callback |
| Client Authentication | Client secret sent as Basic authentication |
| Use PKCE / PKCE Method | On / S256 (pkceEnabled=true, pkceMethod=S256) |
| Validate Signatures / Use JWKS URL | On / On; use the bridge JWKS URL |
| Default Scopes | openid profile; add email only when authorized |
| Disable nonce | Off |
| Access token is JWT | Off: the bridge access token is opaque |
| Store Tokens | Off unless your integration explicitly needs token storage |
| Logout URL / backchannel logout | Unset / Off |
Forward prompt=none |
Off: a new Wallet presentation is required |
Account mapping and linking
Map given_name and family_name; use the external sub association for
identity. Configure first broker login to require authentication to an existing
local account before linking; avoid automatic linking by email. Assign realm
roles locally. An age client's changing sub is unsuitable for ordinary brokered
account creation; this recipe is for identity verification.
Sources: Keycloak OIDC identity brokering and first login, Keycloak outbound PKCE configuration.
Test your installation
Start wallet login from an application using this realm. Verify first login, existing-account linking and a second login to the same account. Use the complete checklist in section 8.12 before enabling customer access.
8.3 OAuth2 Proxy
Prerequisites
An existing website behind OAuth2 Proxy and access to its private server configuration.
Installation and configuration
Use the generic oidc provider to protect an existing server-side website.
First choose the proxy's exact HTTPS callback, normally /oauth2/callback.
Open the registration form, register
that callback as an Identity verification client with verified user email
authorized, and verify your developer email. Copy the issued Client ID and
one-time secret before configuring the proxy.
Configure provider=oidc, oidc_issuer_url, client_id, a privately stored client
secret, redirect_url, scope="openid profile email",
code_challenge_method="S256" and insecure_oidc_skip_nonce=false.
Keep issuer, signature and email verification enabled, and set an explicit email
access policy for your application. Do not require groups or refresh tokens.
Use secure proxy session cookies and your own session expiry. Before opening
access, verify the complete login and access-denial scenarios in your proxy
deployment using the checklist in section 8.12.
Sources: OIDC provider, PKCE, nonce and configuration options.
Test your installation
Open a protected URL while signed out. Complete wallet and contact-email verification, then confirm access to that URL. Check that cancellation denies access. Use the complete checklist in section 8.12 before enabling customer access.
8.4 Nextcloud
Prerequisites
Nextcloud administrator access and a compatible user_oidc app.
Installation and configuration
Install a compatible user_oidc app. Add the bridge provider in Nextcloud's
OpenID Connect administration and copy its callback URL. Before entering client
credentials, open the registration form,
register that exact callback for Identity verification, and verify your
developer email. Copy the issued Client ID and one-time secret into Nextcloud,
then configure Discovery. Map user ID to sub; authorize/request user
email only if needed. Keep the provider-specific user-ID prefix enabled.
Keep use_pkce=true; set send_userinfo_claims=false because this bridge rejects
OIDC claims requests. Set enable_default_claims=false, map only attributes
provided by the bridge, and disable single_logout. Review provisioning before
launch: soft_auto_provision=false avoids automatic matching to accounts in
other backends. Existing accounts need an explicitly authenticated linking process.
Source: Nextcloud user_oidc configuration.
Test your installation
Complete wallet sign-in and check the resulting Nextcloud account. A second login must find the same account without matching another account by email. Use the complete checklist in section 8.12 before enabling customer access.
8.5 Other OIDC applications and libraries
First define your application's exact HTTPS callback and open the registration form. Choose identity or age verification, verify your developer email, and save the issued Client ID and one-time secret on your server. Then configure your OIDC library with these credentials and the common settings. These are library choices, not bridge-specific connectors or completed interoperability certifications:
| Stack | OIDC client | Configuration to map |
|---|---|---|
| JavaScript / Node.js | openid-client | Discovery, client authentication, fresh state/nonce/verifier, S256 and callback validation |
| Python, including Flask / Django / ASGI | Authlib | Discovery, openid profile, S256, server-side state and ID Token validation |
| Java / Spring | Spring Security OAuth2 Login | Issuer URI, client registration, callback and S256 for the confidential client |
| ASP.NET Core | OpenID Connect handler | Authority, Code response type, PKCE and the exact registered token authentication method |
Confirm outbound S256 even for confidential clients. Some libraries default
to client_secret_post; the free registration form issues client_secret_basic.
Configure the library accordingly, or arrange a matching client method with the
support. Request only supported scopes, disable automatic
offline_access, keep nonce/signature/issuer/audience checks enabled and store
the secret server-side. A browser-only SPA needs a server callback/BFF because
the bridge does not enable cross-origin Token/UserInfo exchanges.
For another identity broker, configure its external OIDC source with the common settings and enable outbound S256, nonce and signed ID Token validation. SAML, LDAP, SCIM and Client Credentials are not direct sign-in interfaces of this bridge. A broker can translate into other protocols on its own side after OIDC login.
8.6 WordPress setup
Prerequisites and download
WordPress.org submission is in progress; directory approval/publication is not yet confirmed. Install from the ZIP while review is pending.
Download Talao Wallet Login for EUDI Wallet 0.1.9 — WordPress plugin ZIP. This independent plugin is published by Web3 Digital Wallet (Talao), without official affiliation to the EU or Member State Wallet providers. Install the ZIP directly using the procedure below. Requires WordPress 6.5 or later and PHP 8.1 or later with OpenSSL and Sodium.
Installation and configuration
In WordPress, open Plugins → Add New Plugin → Upload Plugin, select the ZIP, install it and select Activate. Then open Settings → Talao Wallet Login for EUDI Wallet. The setup screen displays five numbered steps in French or English according to the browser's language preferences (English fallback):
- Copy your return URL with Copy URL.
- Select Register my site to open the
registration form in a new tab.
Paste that exact URL into the return URL (
redirect_uri) field. - Complete the form and verify your developer email using the received code.
- On the confirmation page, select Download configuration (JSON). Keep the file confidential: it contains the client secret.
- Return to WordPress and choose the downloaded configuration file. A separate screen shows its filename and the public settings with the secret hidden. Select Save configuration. No manual Client ID or secret entry is required.
After saving, the settings page opens directly on the saved configuration. Use Import another configuration to return to setup. The imported filename is displayed during review and is not stored in WordPress.
New installations default to Age verification (18+). Choose Identity verification if you need user sign-in. Existing configured modes are preserved. Manual configuration is collapsed by default for users who need to enter credentials themselves. Advanced settings shows Issuer, Callback, Token authentication and Scopes. Import validates this WordPress installation's exact callback, purpose and scopes. The JSON is read locally in the browser and is not uploaded to the provider. Saving applies the settings; it does not test Wallet login. Enable contact email only if it was granted to the client. Choose whether to allow new accounts, save, and test the login button. Existing users can link their Wallet from their own WordPress profile after confirming their local password. Select Identity verification to sign users in, or Age verification (18+) to gate public WordPress content without an account. The same Client ID and Client secret fields apply to the selected mode; each mode keeps its own registered client credentials. PKCE and token validation are mandatory. Test each mode separately with your Wallet and registered client, including the callback and final result in WordPress. Saving settings or importing JSON does not validate this full flow.
Upgrading from EUDI Wallet Login
Version 0.1.9 changes the plugin name and directory to talao-wallet-login-eudi.
Deactivate the previous EUDI Wallet Login plugin before activating this ZIP;
do not run both plugins simultaneously. Saved settings, encrypted client secrets,
account associations and tables are preserved. The registered callback URL and
[eudi_wallet_login] shortcode remain unchanged: no new client or JSON import is
required solely for this rename. Test your existing setup after activation.
The 0.1.9 ZIP was uploaded to the existing WordPress.org review on 2 November 2026. The proposed new slug remains subject to reviewer assignment; no directory approval or public listing is claimed.
User journey — identity and age verification
The plugin adds Sign in with your EUDI Wallet to the WordPress login form
and the WooCommerce login form when present. To add it to another page, use the
[eudi_wallet_login] shortcode. In age verification mode, public content is gated
without creating or signing into a WordPress account. This is a site-wide access
check: the WordPress plugin does not provide per-product WooCommerce age rules
or a dedicated age check when an order is placed. Validate WooCommerce login
and your checkout extensions together before enabling them for customers.
After a successful age verification, the plugin returns to the page where verification was started and displays Bravo! Your age has been verified. Select Continue to the site to close the confirmation. This message appears once for that return, not on every page refresh; closing it keeps the temporary five-minute age access. It is shown only after a verified positive result, never on cancellation or from a public URL success parameter. Without JavaScript, a confirmation message remains visible instead of the popup.
The QR screen's Cancel and return to application button sends an OIDC cancellation to the registered callback. The plugin then returns to the local page from which login was started and displays a one-time message. It keeps that page in the encrypted temporary WordPress transaction, separately from the callback, and never accepts an external return URL. Callback errors also use this return after state/browser validation. Missing, expired or invalid transactions show the safe recovery page. A successful login keeps its existing destination. Keep the exact callback registered for your installation.
Accounts and recovery
Account association uses the issuer and sub; the plugin handles the supported
name and email claims automatically. Existing accounts are never linked by email.
New accounts are disabled by default; if enabled, choose Subscriber or Customer
when WooCommerce provides that role. The plugin never assigns an administrator
role. Keep an independent WordPress administrator login while testing.
Test your installation
PKCE S256 and signed token validation are built in; no additional OIDC plugin is needed. Follow the complete login checks in section 8.12 after configuration.
Demonstration
See the WordPress plugin demonstration and installation guide for an explanation in French and a real age-verification button. This public demonstration uses age mode; it does not demonstrate identity sign-in. An English version is also available, with installation instructions and Talao's official contact form.
To try the WordPress example, open the demonstration site, select its age-verification button and approve the age presentation in your wallet. Return to the same browser: the plugin displays Bravo! Your age has been verified. Close the confirmation to continue. This demonstrates the plugin's wallet interaction; this site's explicit public-demonstration setting keeps its explanatory pages accessible, including before verification. On your own protected site, leave that setting disabled to require proof for access.
For a public explanation or demonstration site, enable Advanced settings → Public demonstration site (age mode only). This explicit option keeps all public pages and public REST content accessible without age proof, including to search engines. The button still performs real age verification and displays the success confirmation. It does not restrict any content. Leave it unchecked on a site whose content must require age verification; protection remains enabled by default, including after an upgrade.
8.7 Drupal setup
Prerequisites
Drupal administrator access and a compatible OpenID Connect / OpenID Client Advanced module.
Installation and configuration
With a compatible OpenID Connect / OpenID Client Advanced installation, open
Configuration → People → OpenID Connect, then add an OAuth 2.0 Advanced
client and copy its generated callback URL. First open the
registration form, register that exact
callback for Identity verification, and verify your developer email. Return
to Drupal with the issued Client ID and one-time secret. Set those credentials,
enable automatic discovery with the bridge
issuer, choose scopes, and enable Use PKCE, Send nonce parameter and
Validate ID token signature. Restrict signatures to RS256. Follow the
module instructions
for supplying public keys or JWKS JSON; do not assume a field accepting a JSON
document accepts a JWKS URL. Arrange key updates if the module cannot fetch JWKS.
Use the generated callback for registration and preserve the external sub
association rather than matching existing accounts by email.
Test your installation
Complete identity sign-in and confirm that a second presentation returns to the same Drupal account. Test cancellation using the checklist in section 8.12.
8.8 TYPO3 and MediaWiki
TYPO3 — installation and configuration
For TYPO3, first choose the exact HTTPS redirectUri for your installation.
Open the registration form, register
that callback for Identity verification, and verify your developer email.
Copy the issued Client ID and one-time secret. Then configure the provider in
config/system/oidc.yaml, populate its
clientKey, clientSecret, redirectUri, endpoint and scope settings, and
enable enableCodeVerifier. Map the username from <sub> and the display name
from <given_name> and <family_name>. Leave revokeAccessTokenAfterLogin
disabled: the bridge has no public token-revocation endpoint. Validate the
signature, nonce and S256 checks before enabling frontend or backend login.
MediaWiki — installation and configuration
For MediaWiki, install compatible OpenID Connect and PluggableAuth extensions
and obtain the callback URL documented by the installed extension. First open
the registration form, register that
exact callback for Identity verification, and verify your developer email.
Copy the issued Client ID and one-time secret. Then, in the OpenID Connect provider's data configuration, set providerURL to the
issuer, clientID and clientsecret to the registered credentials, and
codeChallengeMethod to S256. Register the callback documented by the installed
extension. Keep automatic migration by username/email and single logout disabled;
use the extension's issuer/subject association for account identity.
Test your installation
For each CMS, complete identity sign-in, confirm the account association on a second login and test cancellation before enabling access. Use section 8.12.
Sources for the third-party CMS procedures: Drupal Advanced, TYPO3, MediaWiki.
8.9 Moodle setup
Two independent Moodle plugins cover the student journey: sign in with a Wallet, follow a course, pass its assessment, then receive a learning attestation in a personal Wallet. They are developed by Web3 Digital Wallet (Talao).
Both plugins are undergoing Moodle review following automated checks reported as passed. Directory approval and publication remain pending; use the ZIP installation instructions below.
| Plugin | Purpose | Connection and credentials |
|---|---|---|
auth_eudiwallet — EUDI Wallet Login |
Create a student account or link a Wallet to an existing account, then sign in | OIDC bridge; Client ID and secret imported from the registration JSON |
local_eudiissuer — EUDI Wallet Badge issuance |
Deliver an attestation for a course badge already awarded by Moodle | Direct Application API; dedicated API key authorized on an Issuer |
The issuance plugin does not require the authentication plugin: students may also use their institution's existing Moodle sign-in. The two plugins do not share credentials. A free bridge registration creates login access; it does not create an issuance API key or configure a credential Issuer.
Authentication plugin
Prerequisites and download
EUDI Wallet Login for Moodle is available for download. It adds Sign in with your EUDI Wallet to Moodle's login page so students can identify themselves with their Wallet and sign in to their Moodle account.
Download EUDI Wallet Login 0.1.5 — Moodle plugin ZIP.
The dedicated plugin is auth_eudiwallet, available as an alpha release for
Moodle 4.5 LTS, PHP 8.1 or later, with OpenSSL, Sodium and cURL.
Install the ZIP distributed here using Moodle's plugin installation screen. The connection is Moodle → Bridge → Wallet →
Moodle; no Keycloak or additional OIDC plugin is required.
Install and register your Moodle application
- In Site administration → Plugins → Install plugins, upload the ZIP and
confirm its installation. Keep a local administrator login for recovery.
If your hosting provider makes the Moodle source directory read-only, ask
its administrator to install the ZIP under
auth/eudiwallet/and run Moodle's plugin upgrade procedure. - Open Plugins → Authentication → EUDI Wallet Login → Configure EUDI Wallet
Login. Copy the exact HTTPS callback displayed by the plugin, normally
https://moodle.example/auth/eudiwallet/callback.php. - Before configuring the connection, open
free client registration.
Select Identity verification and register that exact callback. Authorize
openid profile emailand the verified contact email claim (email_verified). Verify your developer email, then download the private configuration JSON. The developer contact identifies who registers the application; it does not set the email address of students who will sign in. - Import the JSON in Moodle, review its public settings and save. No manual Client ID or secret entry is required. The secret is encrypted in Moodle and used automatically. Keep the downloaded JSON private.
- The page confirms Client configured and shows whether Wallet login is enabled. Registration and replacement JSON controls are collapsed; you do not need to import the file again after saving.
- To allow students without an existing account to register, select Allow Moodle accounts to be created and save. This is off by default and also respects Moodle's site-wide account-creation policy. Keep it off when only explicitly linked existing accounts should be accepted.
- Follow Open Manage authentication, then enable EUDI Wallet Login using the eye icon. Saving a client configuration alone does not enable the authentication method.
Choose the student's starting point
There are two initial journeys. Use Flow 1 if the student does not have a Moodle account yet. Use Flow 2 if they already have a manual-password account and want to attach their Wallet to it. Once the Wallet is associated with an account, subsequent sign-ins use the Wallet button directly; the student does not repeat account creation or linking.
Flow 1 — First Wallet sign-in for a new student
Administrator prerequisite: enable Allow Moodle accounts to be created in the plugin and ensure Moodle's site-wide policy also permits account creation. This option is disabled by default. It is not needed for linking an existing account through Flow 2.
- Open Moodle's login page while signed out. For a test, keep your administrator session in its normal window and use a private browsing window for the student.
- Select Sign in with your EUDI Wallet (Se connecter avec mon EUDI Wallet).
- Present the requested identity in the Wallet and authorize the presentation.
- If the bridge requests a contact email, enter the student's address and confirm the emailed code. For a new test student, use an address that is not already assigned to the administrator or another Moodle account.
- Return to Moodle in the same browser where the sign-in started. On success, Moodle creates the student's account, associates it with the Wallet and signs the student in. The account has no usable local password; future sign-ins use the Wallet.
Creating the account does not enrol the student in a course or grant teacher or administrator privileges. Moodle continues to manage enrolments and roles.
If Moodle displays An account already uses this email, it has not merged or signed into that account. If it is the student's existing account, follow Flow 2. If the address belongs to someone else, use the student's own contact address. Do not attach a student's Wallet to the administrator account to bypass this error.
Flow 2 — Link a Wallet to an existing Moodle account
This flow supports an existing manual-password account. It keeps that account, its profile, course enrolments and permissions. It does not create a second account.
- Sign in to the existing Moodle account using its usual username and password. Check that you are in the student's account, not the administrator's account.
- Open Preferences → Link my EUDI Wallet (Préférences → Lier mon Wallet EUDI).
The linking page is
/auth/eudiwallet/link.phpon your Moodle installation, for examplehttps://moodle.example/auth/eudiwallet/link.php. - Enter that account's local Moodle password to confirm the linking request, then select Sign in with your EUDI Wallet. The password stays in Moodle; it is not sent to the bridge or the Wallet.
- Present your identity with the Wallet. Complete the bridge's separate contact email verification if requested, then return in the same signed-in browser.
- Moodle displays Your Wallet is linked to this Moodle account (Votre Wallet est lié à ce compte Moodle) and returns to Preferences.
- To test the association, sign out, open Moodle's login page and select Sign in with your EUDI Wallet. Present the same identity: Moodle signs you into the existing account, with its existing courses and permissions.
Keep the local login available for recovery. If the password is unknown, use Moodle's password-recovery process or contact the administrator before linking. LDAP, SAML and other external-account linking methods are outside this alpha. A Wallet already associated with another account cannot be reassigned through this flow. Accounts are never associated automatically by matching email addresses.
Sign in again after either flow — email verification
After Flow 1 or Flow 2, the normal return journey is Moodle login page → Sign in with your EUDI Wallet → identity presentation → the same Moodle account. There is no need to reopen the linking page or re-enter the local password for an ordinary Wallet sign-in.
The contact-email challenge is performed separately by the bridge. The address
is not certified by the identity Wallet. This plugin requires
email_verified=true in the signed token.
Students do not have to enter an email code at every login:
- While the Moodle session remains active, no new Wallet presentation or email challenge is needed. Moodle controls its own session duration.
- When a new Moodle login is needed, the student presents their identity with the Wallet again. The bridge can reuse the verified contact email for the same identity and sector for 30 days from its verification.
- Reusing the email does not extend those 30 days. After expiry, or if the stored contact has been removed or is unavailable, a new email code is needed. Requesting a contact-email change also requires a new verification.
Moodle finds the existing account using (issuer, sub), not the email address.
A newly verified email therefore does not create another account as long as
that identity binding remains unchanged. It does not automatically overwrite
an existing Moodle profile email either. The bridge's subject-derivation limits
still apply if identity attributes or the application's sector change.
Accounts are associated by (issuer, sub), never by email. Existing local
profile data is not overwritten on subsequent logins. A verified identity does
not itself prove student status: course enrolments and roles remain controlled
in Moodle. Identity attributes do not grant teacher or administrator privileges.
The plugin implements PKCE S256, nonce, signed token validation, UserInfo binding and expiring single-use state. Configure Moodle cron and disable caching/logging of callback query strings. Tokens, codes and identity documents must not enter access/debug logs. Saving a configuration does not validate a complete Wallet flow. Validate your installation using section 8.12 before giving access to students.
This plugin authenticates students. Learning-attestation delivery is available
in the separate local_eudiissuer plugin described below. Age verification is
not a mode of this Moodle authentication plugin.
Sites already using Keycloak can retain an identity-broker setup using section 8.2 and Moodle's OAuth 2 services; that is a separate integration from this plugin. See Moodle authentication configuration.
Issuance plugin
Prerequisites and download
Download EUDI Wallet Badge issuance 0.1.1 — Moodle plugin ZIP. This alpha release requires Moodle 4.5 LTS / PHP 8.1 or later, with cURL, Sodium and GD. Follow the configuration instructions below. The downloaded ZIP also includes a README; the Application API documentation describes the server-to-server contract.
The connection is Moodle → issuance API → Wallet, without the OIDC bridge. Our issuance service manages credential signing, the credential offer and the Wallet protocol. Moodle checks the student's eligibility and sends the award attributes over an authenticated server-to-server API.
Before configuring this plugin, arrange a dedicated issuance API client with us (contact@talao.io). It must be authorized on your institution's Issuer. Obtain the HTTPS API origin, Issuer slug, credential configuration identifier and private API key. The API key is separate from the OIDC Client ID and secret; none is included in the ZIP. If a key is lost, contact us to arrange a replacement, then update Moodle securely. Do not send keys by email or put them in browser code.
The learning-attestation model uses a HAIP Issuer, an EAA in dc+sd-jwt
format, and configuration learning_achievement_sd_jwt with VCT
urn:talao:learning-achievement:1. Configure the institution's signing identities,
validity and revocation policy on the issuance service before enabling delivery.
This is a learning-attestation model inspired by the awarded-badge approach;
it does not claim Open Badges 3.0 conformance, a qualified EAA or recognition as
a diploma.
Configure course completion and delivery
- Install the ZIP through Site administration → Plugins → Install plugins
(type Local plugin), or have your administrator install it under
local/eudiissuer/and run Moodle's normal plugin upgrade. - Open Site administration → Plugins → Local plugins → EUDI Wallet — Badge
issuance. Enter the API HTTPS origin (our service:
https://openid4vc-hub.com), your authorized Issuer slug and API key. Save; the key is encrypted and never redisplayed. A blank key field keeps the saved key. Protect Moodle's private encryption key and backups. - Select Verify the saved API connection. Success confirms API authentication only; it does not prove authorization on the chosen Issuer or a completed Wallet issuance.
- In the course, configure a final quiz and its passing grade. Set activity completion to require a passing grade, then create a native Moodle course badge whose criteria require that completion. Enable the badge. Moodle awards it when the criteria are satisfied; configure Moodle cron. A teacher's quiz preview does not constitute a student's successful attempt.
- Associate that badge with the existing credential configuration, normally
learning_achievement_sd_jwt. Several course badges can use this same model; a new credential template is not required for each course. - Enable both the badge mapping and the delivery service. Both start disabled.
Add My Wallet attestations (
/local/eudiissuer/index.php) to the course's certificate section, or use the plugin's course/global navigation entry. Validate the recipient policy and a complete Wallet flow before opening access to students.
Student journey — from quiz to personal Wallet
- The student signs in using their Wallet or an existing Moodle login, enrols in the course and completes the lessons and final assessment.
- Moodle awards the course badge according to its configured criteria. The student opens My Wallet attestations. Only enabled, unexpired course badges actually awarded to this account are eligible; the account must be active and enrolled in the visible course.
- The student requests their attestation. Moodle creates an offer valid for
five minutes and displays a locally generated QR and a Wallet-opening
link. The Pre-Authorized Code request omits
tx_codeentirely: there is no separate transaction code to enter. Keep the QR and link private. - The student scans the QR or opens the link with their Wallet and accepts the credential. The issuance service performs the effective HAIP Wallet checks; being signed in to Moodle does not bypass them.
- Check delivery status distinguishes an issued credential from confirmed Wallet acceptance. Acceptance is confirmed only when the Wallet sends the corresponding notification; absence of that notification does not prove that storage succeeded or failed.
The attestation contains the student's first and last name from their Moodle profile, an opaque award reference, badge identifier, title, description, criteria, award date and awarding organization. The plugin does not send email, detailed grades, Moodle user ID or the OIDC subject. Profile names are institutional account data, not a new identity verification by the issuance plugin. A Moodle session alone does not cryptographically bind the receiving Wallet to the named student; the institution must validate its recipient policy.
The credential remains in the student's personal Wallet after issuance. Deleting a Moodle account or badge does not erase or automatically revoke it. The authorized issuer manages revocation through the issuance service. Temporary offers are encrypted and cleared on expiry or confirmed issuance; Moodle retains award/request references and statuses, not the credential itself.
Retry and delivery status
Repeated requests reuse the pending offer. After a remotely confirmed expiry or failure, an explicit retry is possible if nothing was issued, with at most three offers per award. An uncertain API timeout blocks another creation until reconciliation.
Demonstration — the complete student journey
Open the Moodle demonstration website. Its student journey illustrates how the two plugins work together:
- Open the login page and select Sign in with your EUDI Wallet. A new student follows Flow 1; an existing account must first use Flow 2. Use the student's contact email, not an administrator's address.
- Open the demonstration course after signing in. Enrol if offered, follow its learning activities and complete the final multiple-choice assessment. Course access and badge eligibility remain subject to Moodle's settings.
- Once Moodle has awarded the eligible course badge, open My Wallet attestations. Request the attestation and scan its QR with a compatible wallet. Without an eligible awarded badge, there is no attestation to retrieve.
- Accept the EAA learning attestation in SD-JWT format in the wallet. The offer requires no separate transaction code. See the issuance section below for the model, validity, delivery status and recipient-binding limits.
Identity authentication has been tested with France Identité, and learning attestation delivery with the Talao wallet. These are distinct journeys: the identity wallet used to sign in does not have to be the wallet used for delivery. Students do not need a bridge-registration JSON or an issuance API key to use this configured demonstration.
8.10 Shopify — age verification at checkout
Add EUDI Wallet age verification to your Shopify store and choose which products require customers to prove they are 18 or over. Customers can browse your catalogue; checkout is blocked for restricted products until their age has been verified. Products without an age restriction remain available without proof.
The EUDI Wallet 18+ block adds a verification button to your theme. A server-side checkout rule enforces the age requirement for the products you select.
The demonstration journey has been tested with the France Identité and Talao wallets. The application provides a French/English interface and assistance in French from Web3 Digital Wallet (Talao), a French company. These tests do not imply certification or endorsement by France Identité. Contact us for integration assistance.
Prerequisites and download
Download the 0.1.8 Shopify application source ZIP and follow
our complete Shopify installation guide. The package
includes the Python backend, theme extension and checkout Function. You deploy
it in your own Shopify organization and host the backend on your HTTPS server;
shopify app deploy deploys the Shopify extensions, not the backend.
For custom-app deployment with Functions on a production store, Shopify Plus is required. A Plus development store is suitable for initial tests. Check the plan/distribution requirements in the installation guide before proceeding. The application was submitted for Shopify App Store review on 30 October 2026. Approval and publication are pending. Until the application is available there, follow the source installation guide or contact us to plan your installation. The source ZIP is not an App Store installation.
Installation and configuration
- Complete backend installation and extension deployment using the
installation guide, then open the application's configuration screen.
Create a separate bridge age client before importing any settings. Copy
the exact HTTPS callback displayed by the application
(
https://YOUR_APP/oidc/callback) and follow its free registration link. Use a stable hostname: changing the callback hostname requires a new bridge client; editing the existing client's hostname is not supported. - Choose Other OIDC application, Age verification and HTTP Basic.
Verify your developer email, then download the private configuration JSON.
The bridge callback is distinct from the Shopify OAuth
/oauth/callback. - Import the JSON into the Shopify application and click Save. The client secret is stored encrypted and is not displayed again. No Application API key is required; bridge connection quotas apply.
- With the checkout extension deployed, activate 18+ validation in the application and check that the rule is active in Shopify's checkout settings. Select and save each product that requires proof of majority.
- In your Shopify theme editor, choose Add section → Apps → EUDI Wallet 18+. Enter the application's HTTPS origin without a trailing slash, then save.
Shopify merchant authorization and wallet verification are separate. The backend requests an expiring Shopify offline access token and stores its refresh token encrypted to renew API access server-side. After upgrading an installation that used a permanent token, open the app's installation flow once again to authorize it through Shopify. Keep the existing bridge credentials unless its callback hostname also changed. These API-token lifetimes do not change the daily age authorization described below.
Customer journey and expected result
- The customer signs in to their Shopify customer account and adds a restricted product to the cart. Checkout requires age verification.
- They select the EUDI Wallet button, open a compatible wallet and approve the request for proof that they are 18 or over.
- After successful verification, they return to the cart and continue to checkout. The authorization lasts until the end of the store's calendar day; after that, a new age verification is required.
This integration requires a signed-in customer account. Signing into Shopify administration does not sign you into a customer account; the theme block offers customer sign-in when needed. It verifies age and does not replace Shopify's customer authentication.
Shopify receives a minimal age authorization associated with the customer account, rather than the wallet holder's name, date of birth or identity attestation. The checkout rule checks this authorization on the server. When the operator enables the retention task, expired authorization content is replaced by an empty JSON object, guarded against concurrent renewal. An empty metafield grants no access; this does not delete the customer account or the metafield itself. See the maintenance guide.
The standard connector provides wallet verification. The merchant chooses which products require proof and organizes any separate alternative customer process. The connector does not provide an active human fallback by default. If no valid authorization is recorded, checkout remains blocked for restricted products.
From version 0.1.7, an optional merchant-review module can be made available by the connector operator. Each merchant then decides whether to enable requests in their app settings and explicitly commits to checking the queue, performing appropriate checks and providing reasoned responses. Web3 Digital Wallet supplies the tool; the merchant performs the review. Every store starts with this option off, including after upgrade. Disabling intake preserves existing cases and follow-up. A pending request never unlocks checkout; a merchant approval is a daily merchant authorization, not a wallet verification. See the installation guide for configuration and operating duties.
If wallet verification succeeds but no authorization is recorded in Shopify,
check the application’s Shopify customer-data access as well as its
write_customers scope. The scope alone does not establish protected
customer-data access. Follow the installation guide’s troubleshooting
section; creating another
bridge client does not resolve a Shopify API access denial.
Demonstration
Try the Shopify demonstration store.
Visitor password: demo-eudi. This opens the demonstration store only;
it does not grant customer-account access or replace age verification. Compare two
fictional products: Vin rouge — démonstration 18+, which requires proof of
majority, and Jus de raisin — démonstration tout public, which does not.
The store is a demonstration: no alcohol is sold or delivered, and payments
must remain in test mode.
Use a compatible age attestation in France Identité or Talao to try the wallet step. You need a Shopify customer account, distinct from the store's administrator account. After a successful presentation, the confirmation page displays “Your age has been verified” and offers a return to the cart. The authorization is recorded for the signed-in customer until the end of the store day. A restricted checkout must remain blocked after cancellation or an expired request when that customer has no earlier valid authorization.
The demonstration uses a Shopify development store. Shopify requires a visitor password for these stores. For a publicly accessible store without this password, contact us to plan a separate production installation with the appropriate Shopify plan and app distribution.
8.11 AI agents — MCP age verification
EUDI Wallet Connect — MCP age verification 0.1.2 alpha lets an AI agent
request proof that a user is 18 or over. The user performs the presentation
in their own wallet. The agent receives a status and, only after successful
verification, age_over_18: true. It does not receive the user's name, email,
birth date or complete credential.
Download EUDI Wallet age verification — MCP ZIP. Published by Web3 Digital Wallet (Talao). Create your free MCP client · Contact.
Prerequisites and components
The downloadable connector supplies a local MCP server using stdio and a bilingual
French/English browser page listening only on 127.0.0.1. The host application
starts the process and discovers two tools:
| Tool | Input | Output |
|---|---|---|
start_age_verification |
No arguments; obtain the user's approval first | Opaque verification_id, private verification_url, expiration and polling interval |
get_verification_status |
The returned verification_id |
pending, verified, failed or expired; age_over_18: true only with verified |
The connection is AI application → MCP connector → OIDC bridge → wallet. The bridge performs the age-verification journey; the connector is an OIDC relying party. It uses the same registration and Free allowance as the CMS connectors, without a verification API key.
Installation and configuration
- Open free MCP registration.
Choose MCP · AI agents, Age verification (18+), and the exact callback
http://127.0.0.1:5088/callback. Enter your developer contact, accept the policies and verify your email with the received code. - Download the configuration JSON. This is a public OIDC client:
token_endpoint_auth_method=none, no secret,openid age_verification, mandatory PKCE S256. The verified email is your developer contact, not an attribute requested from the wallet holder. - Download and extract the connector ZIP. With Python 3.11 or later, run:
bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
chmod 600 /PRIVATE/eudi-age.json
Save the downloaded JSON at that private path, outside source control.
Use it unchanged with --config; do not put it in a prompt. On Windows,
use .venv\Scripts\python.exe and equivalent private file permissions.
4. Add the server to your AI application using absolute paths to Python,
server.py and the JSON. The application starts the process; do not start
another copy on the same port. If 5088 is occupied, register a different
fixed port and use that client's JSON.
Only public age clients can register this narrowly scoped HTTP IPv4 loopback
callback (127.0.0.1, explicit unprivileged port, /callback, no query).
Redirect matching remains exact, including the port; no wildcard or dynamic
port matching is provided. Web and confidential clients still require HTTPS.
Do not register this client for identity sign-in, or expose its local listener.
Connect Claude, Codex and other AI applications
| Application | Connector configuration |
|---|---|
| Codex local CLI, IDE or desktop interface with MCP support | Add a stdio server using codex mcp add or its existing mcp_servers configuration |
| Claude Code | Add a local server with claude mcp add --transport stdio |
| Claude Desktop | Merge an eudi-age entry into the existing mcpServers configuration, then restart |
| VS Code / GitHub Copilot | Add a stdio server in .vscode/mcp.json using the servers key |
| Cursor | Add a local stdio entry to .cursor/mcp.json using mcpServers |
| ChatGPT web or another cloud-hosted assistant | Use the event website for the demonstration; a native cloud integration requires the remote deployment described below |
For example, after replacing all three absolute paths, register it in Codex:
codex mcp add eudi-age -- /ABS/eudi-age/.venv/bin/python /ABS/eudi-age/server.py --config /PRIVATE/eudi-age.json
codex mcp list
For Claude Code:
claude mcp add --transport stdio eudi-age -- /ABS/eudi-age/.venv/bin/python /ABS/eudi-age/server.py --config /PRIVATE/eudi-age.json
claude mcp list
Open INTEGRATIONS.md, included in the downloaded connector ZIP, for Claude Desktop, Codex TOML, VS Code and Cursor JSON examples, Windows paths and troubleshooting. It also links the official publisher documentation. After configuration, complete a wallet verification in your chosen AI application and check its success, cancellation and expiry handling. No OpenAI or Anthropic API key is required by this connector; the AI application manages its own model access.
User journey and expected result
- The user asks, for example: “Verify that I am 18 or over with my EUDI
Wallet and show me the private link.” The AI application obtains approval
and calls
start_age_verification. - The agent displays the returned link. The user opens it in a browser on
the same computer as the MCP process. A
127.0.0.1link from a cloud runtime does not point to the user's computer. - Click the verification button. The HTTPS bridge displays the QR to scan with a compatible wallet, or its wallet-opening button. Opening the computer's loopback link directly on a phone will not reach the companion running on the computer.
- The user reviews the request in their wallet and authorizes presentation. The agent must not operate the wallet on their behalf.
- The agent calls
get_verification_statuswith the original identifier, no more frequently than every three seconds. Onlyverifiedtogether withage_over_18: trueconfirms the majority proof. The browser also displays a translated success message after the OIDC callback. Pending, failure, expiry or an unavailable service never confirms age.
The proof uses the European age attestation eu.europa.ec.av.1 in mso_mdoc,
requesting only age_over_18. Cryptographic validation remains behind the bridge.
The connector validates the signed ID Token (RS256, issuer, audience, nonce,
timestamps, at_hash) and matching UserInfo, requiring boolean age_over_18: true
in both. Callback state is single-use and bound to a browser cookie; the token
exchange uses PKCE S256. Tokens and claims never become MCP tool output.
See the age-verification technical details.
Native connector privacy, limits and remote deployment
Each local process is for one owner. It allows at most three active requests and ten starts per minute, including failed starts. The bridge's own limits apply in addition. Requests expire after at most five minutes. State is volatile; stopping the process loses its local requests and resets its local counters, but does not cancel existing bridge transactions or reset their quotas. A new process cannot retrieve another process's result.
Free currently includes 100 verification attempts per UTC calendar month and 10 per UTC day and three concurrent
sign-ins; the registration page gives the effective limits. Local MCP clients
share these budgets by verified developer email (a pseudonymous HMAC reference),
so unrelated developers using 127.0.0.1 do not share one global budget. Web
clients still share by callback hostname. Creation is limited to three attempts
per day per IP and verified developer email. Failed and abandoned attempts count;
bridge limits of 60 starts/minute/client and 10/minute/IP/client also apply.
The QR and browser link are private temporary capabilities. Do not publish them or paste them into support tickets. The connector keeps no durable copy of the credential and clears its nonce, PKCE verifier and OAuth state on a terminal result or expiry. The AI host may retain the conversation and its links under its own policies; the verification service has its own retention policy. A majority proof does not identify the user, log them into an account or establish that the wallet holder is the author of the conversation. Business applications must enforce access on the server, rather than trusting a sentence generated by the model.
For a shared remote MCP service, additional work is required: HTTPS, Streamable HTTP, suitable MCP authorization, per-caller ownership and shared quota/state management. A supported private tunnel may be explored under the AI host's policies, but is not configured by this package. The browser companion on port 5088 is not an HTTP MCP endpoint; do not enter it as a remote MCP URL or expose it publicly. The OIDC bridge is not currently a ready-made MCP OAuth authorization server. No public MCP URL or marketplace listing is announced.
Demonstration — GPT event assistant
Talao Events demonstrates how an AI assistant can request a wallet age proof before opening access to an adult event: French demonstration · English demonstration.
Visitors need a compatible wallet with an age attestation. They do not need to install the MCP connector, register an OIDC client or supply an OpenAI API key. The site uses a real GPT conversation and real MCP tool calls. Its event catalogue and booking sheets are fictional: no ticket, payment or reservation is created.
- Choose an event, or ask the assistant to help choose one. Neon Session and Blue Notes Club require an age proof; Sunset Live is open to all ages.
- Read the notice and tick the checkbox before sending a message to OpenAI. Do not enter personal or confidential information in the conversation.
- For an adult event, ask to verify your age. The assistant calls
start_age_verification; the page displays a verification button. - Click that button. The bridge displays a QR code or a button to open a compatible wallet. Review and approve the request in your wallet.
- Return to the same browser. The page checks the result without additional
GPT calls. A success dialog and a green banner confirm the proof and offer
Explore my 18+ offer. Only
verifiedtogether withage_over_18: trueunlocks the adult event's fictional backstage experience. Closing the dialog leaves the offer accessible through the banner and event cards until expiry. Use Run the test again on the offer page to clear the current conversation and proof; this does not reset quotas.
Worked example — Neon Session
Send “I would like to attend Neon Session. Verify that I am 18 or over with my EUDI Wallet.” In French: « Je veux participer à Neon Session. Je souhaite vérifier mon âge avec mon EUDI Wallet. » The assistant prepares the request; you click the verification button and authorize the wallet presentation.
After a successful return, the page shows “Success, your age is confirmed!” (« Bravo, votre majorité est confirmée ! »). Select Explore my 18+ offer (Découvrir mon offre 18+) to open Neon Session — the backstage experience: a fictional artist meeting, backstage tour and private music session. The offer page provides Run the test again (Rejouer le test).
The minimal result used to unlock access is:
{"status": "verified", "age_over_18": true}
This is a result returned by the server, not a message to paste into the chat. The server checks the current proof again when opening the offer. Expiry or an unavailable result removes the success banner and offer links; directly opening the protected offer without a valid proof is also refused.
Saying “I am over 18” does not unlock access. Neither the model's text nor a query parameter grants permission: the website checks the result on its server. A refused, failed or expired presentation leaves the adult event locked. The all-ages event can be explored without presenting an attestation.
What is shared and retained? OpenAI receives the conversation and the minimal
verification status, including the boolean age result after success. It receives
no credential, identity attributes, verification link or identifier, OIDC token,
authorization code or secret. The technical panel shows tool names and statuses.
The application requests store=False from OpenAI; this does not replace
OpenAI's own applicable data-retention policies.
The site's conversation state is encrypted in temporary storage with a 15-minute TTL refreshed on update. Its private MCP worker also keeps encrypted verification state temporarily. Proof expires after at most five minutes; an expired result cannot unlock an adult event. Resetting the conversation clears its current state but does not reset quotas. The demonstration defaults to five chat turns per minute per IP, 20 per UTC day per browser session and 200 per UTC day across the site. A turn can make up to two model calls; bridge verification quotas apply separately.
How does this relate to the downloadable connector? The website is an MCP
client using a private stdio worker. Its browser does not connect directly to
MCP, and its URL is not a remote MCP endpoint to enter in Claude or ChatGPT.
To add the two tools to your own AI application, follow the installation steps above.
| Integration | OIDC client to register | Callback |
|---|---|---|
| Downloadable connector running on your computer | MCP · AI agents, age verification, public client | http://127.0.0.1:5088/callback |
| Hosted Talao Events website | Other OIDC application, age verification, separate web client | https://mcp-demo.openid4vc-hub.com/mcp-demo/callback |
For your own assistant website, keep the client JSON and OpenAI API key on the server. The native connector's loopback JSON cannot be reused for this website. Neither integration needs an Application API key to verify age through the bridge.
8.12 Verify your integration
Use staging and a new non-privileged account first.
| Integration | Successful result to verify | Negative test |
|---|---|---|
| Identity sign-in | The user is signed in; a second login finds the same account | Cancellation or an expired callback creates no session |
| Age verification | A valid 18+ proof unlocks only the intended content or checkout | No proof, refusal or expiry leaves access blocked |
| Moodle issuance | An eligible awarded badge produces an attestation received in the wallet | An ineligible account cannot request an offer |
| MCP | Both verified and boolean age_over_18: true are returned |
A model's statement alone never grants access |
For identity integrations, complete a Wallet login, the CMS callback and code exchange, then verify that a second login returns to the same account. If email is requested, verify that a changed contact address does not change the account's identity binding. Also check cancellation, expired transactions and replayed callbacks: none should create a logged-in session.
The CMS must validate the signed ID Token and match UserInfo sub to its subject.
Keep tokens, codes, verifiers and identity values out of diagnostic logs.
Successful Discovery or a plugin's connection test alone does not validate login.
Record the CMS/module versions and completed tests for your deployment.
9 Errors and limits
Some products require more than Authorization Code login. For example, Shopify's custom identity-provider requirements include RP-Initiated Logout, which the bridge does not implement. Such a product needs an adapter or additional service capabilities before direct integration. The bridge does not supply refresh tokens, SAML, SCIM, groups or roles.
| Error | Action |
|---|---|
| Local HTTP 400 before redirect | Fix client ID, exact callback or duplicated parameters |
invalid_request |
Check S256 challenge/verifier, response mode and supported parameters |
invalid_scope |
Request only registered scopes, including openid |
login_required |
Restart with interactive login rather than prompt=none |
access_denied |
User declined; do not silently restart indefinitely |
invalid_client |
Check authentication method, secret and client enabled state |
invalid_grant |
Expired/replayed code, wrong callback or PKCE mismatch; start again |
| HTTP 429 | Respect Retry-After and back off |
| HTTP 503 | Service unavailable; back off, then start a new login |
Never log codes, tokens, verifiers, callback query strings or identity credential claims. Client revocation prevents further authorize/code/token/UserInfo use. It does not instantly invalidate an ID Token already received by your application or close your application's existing session. Plan your own session expiry and recovery.
10 Diagnostic downloads
For support, the original browser can access /login/<transaction>/trace; the
hosted login does not display a technical diagnostic-download link. It exports the
bridge stages, verification service HTTP/verification outcomes and attribute presence/types rather
than identity values. The sandbox test client offers its own trace on success or
failure, covering Discovery, callback state, code exchange, ID Token validation
and UserInfo. Both contain the same hashed-state correlation when the request
has state, allowing support to match the two files without sharing raw state.
Download from the browser that initiated the login. Traces are encrypted at rest, expire after ten minutes and require the owning browser cookie. They never contain tokens, authorization codes, nonce, PKCE verifier, credentials, identity credential values, API keys or signing keys. A bridge trace also carries the verification service presentation ID so our support team can locate the corresponding verification-service trace. Exported files are technical diagnostics, not proof of successful authentication. The generic bridge cannot capture errors internal to an arbitrary relying party; that application must supply its own diagnostics.
11 France Identité compatibility
The official France Identité Partenaires overview
describes a partner test wallet with mdoc identity/age credentials and OID4VP 1.0
online presentation on Android and iOS, including same-device and cross-device
flows. It also documents support for the European av:// age-verification
blueprint on Android 1.3.5078 or later, while marking that capability as in
progress on iOS. These documented formats and flows explain the protocol basis
of the bridge's integration. The identity configuration described below has been
tested with France Identité using x509_hash. Check the wallet's published
version and channel support when preparing your integration.
Use the official OID4VP guide and age verification guide for Wallet setup, supported channels and release limitations. The Partenaires build is distributed to testers through Firebase App Distribution / TestFlight; these capabilities must not be assumed for every consumer app release.
11.1 Attestations and requested attributes
This section describes the Wallet-facing exchange for protocol specialists. Applications integrating WordPress, Keycloak or another OIDC client still use Discovery and the OIDC endpoints above. The bridge obtains the Wallet request from its verification service and displays it as a QR code or deep link; your application does not build that request.
| Parameter | Identity verification — France Identité compatibility | Age verification — 18+ |
|---|---|---|
| Attestation | Personal Identification Data (PID) mdoc | Proof of Age mdoc, separate from the PID |
| OID4VP credential format | mso_mdoc |
mso_mdoc |
Document type (doctype_value) |
eu.europa.ec.eudi.pid.1 |
eu.europa.ec.av.1 |
| Namespace | eu.europa.ec.eudi.pid.1 |
eu.europa.ec.av.1 |
| DCQL credential query ID | pid_mdoc |
proof_of_age |
| Requested attributes | family_name, given_name, birth_date, birth_place |
age_over_18, required to be boolean true |
| Attribute representation | Names and birth_place are text; birth_date is a date string |
Boolean age threshold; no name or birth date |
| Retention intent in each claim request | intent_to_retain: false |
intent_to_retain: false |
| Cryptographic holder binding | Required | Required |
The identity query uses the explicitly selected France Identité compatibility
policy. Its text birth_place is distinct from the current EU PID Rulebook's
structured place_of_birth; the two encodings are not silently substituted.
Neither query requests nationality, portrait or email. Optional OIDC contact
email is collected and verified separately by the bridge after identity verification.
11.2 Identity request: OID4VP 1.0 and HAIP 1.0
Try the France Identité identity example — HAIP #3
to inspect the wallet-facing identity flow. The bridge uses the same credential
query, x509_hash identification, encrypted response mode and client metadata
structure. Session URLs, nonces, timestamps, certificate hashes and ephemeral
keys are specific to each request.
The configured identity verifier uses
OID4VP 1.0 Final
with the redirect flow of
HAIP 1.0 Final, section 5.1:
a signed Request Object retrieved through request_uri, x509_hash verifier
identification, and an encrypted direct_post.jwt response. Public response
encryption keys are ephemeral and advertised in client_metadata.jwks.
No separate relying-party registration certificate is included in this
compatibility request. This does not mean that the request is certificate-free:
the signing certificate chain is carried in the JWS x5c header, and the
x509_hash client identifier binds to the leaf certificate. An OIDC application's
registration and Client ID are a separate, mandatory step.
France Identité compatibility: identity verification has been tested with
x509_hash, OID4VP 1.0 and the HAIP 1.0 redirect flow, without a separate
relying-party registration certificate. Check the supported wallet version and
trust environment when configuring your integration.
The following is an illustrative wallet request. Angle-bracket values are placeholders;
handles, nonces, certificate hashes and keys are generated afresh per transaction.
The configured invocation scheme is haip-vp://; a different invocation scheme
requires explicit compatibility with the target Wallet.
haip-vp://?client_id=x509_hash%3A<CERTIFICATE_SHA256_BASE64URL>&request_uri=https%3A%2F%2Fverifier.example%2Fverifier%2Fidentity%2Frequest%2F<REQUEST_HANDLE>&request_uri_method=post
The Wallet retrieves the signed JWT with an HTTPS POST to request_uri, using
Content-Type: application/x-www-form-urlencoded and
Accept: application/oauth-authz-req+jwt. If supplied, its wallet_nonce is
included in the signed payload. Relevant decoded payload fields are shown below;
Only JWT timing/ID fields and optional wallet_nonce are omitted for readability.
client_metadata includes the configured format capabilities, the ephemeral
P-256 public response-encryption key and the supported content-encryption
algorithms. The example public key was generated solely for this documentation;
its private counterpart is not retained. A real transaction uses its own fresh
key pair; only the public JWK is sent to the Wallet. The -7 COSE identifier
means ES256 for mdoc issuer and device authentication. Advertising both supported
formats does not change the DCQL request: this transaction asks only for an mdoc.
{
"iss": "x509_hash:<CERTIFICATE_SHA256_BASE64URL>",
"aud": "https://self-issued.me/v2",
"client_id": "x509_hash:<CERTIFICATE_SHA256_BASE64URL>",
"response_type": "vp_token",
"response_mode": "direct_post.jwt",
"response_uri": "https://verifier.example/verifier/identity/response/<RESPONSE_HANDLE>",
"nonce": "<FRESH_NONCE>",
"dcql_query": {
"credentials": [
{
"id": "pid_mdoc",
"format": "mso_mdoc",
"meta": {
"doctype_value": "eu.europa.ec.eudi.pid.1"
},
"claims": [
{
"id": "family_name",
"path": [
"eu.europa.ec.eudi.pid.1",
"family_name"
],
"intent_to_retain": false
},
{
"id": "given_name",
"path": [
"eu.europa.ec.eudi.pid.1",
"given_name"
],
"intent_to_retain": false
},
{
"id": "birth_date",
"path": [
"eu.europa.ec.eudi.pid.1",
"birth_date"
],
"intent_to_retain": false
},
{
"id": "birth_place",
"path": [
"eu.europa.ec.eudi.pid.1",
"birth_place"
],
"intent_to_retain": false
}
],
"require_cryptographic_holder_binding": true
}
]
},
"client_metadata": {
"vp_formats_supported": {
"dc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
},
"mso_mdoc": {
"issuerauth_alg_values": [
-7
],
"deviceauth_alg_values": [
-7
]
}
},
"jwks": {
"keys": [
{
"crv": "P-256",
"kty": "EC",
"x": "917gXN1LScPlIm4Jr7yA9sjnAjTcC6fnLaNxoUQT97Y",
"y": "e8AYcCurA_shZKJOA7ZgcjbCALGmPiMcVphVjYkX3Ug",
"kid": "example-ephemeral-response-key",
"use": "enc",
"alg": "ECDH-ES"
}
]
},
"encrypted_response_enc_values_supported": [
"A128GCM",
"A256GCM"
]
}
}
The Wallet returns an encrypted response JWT to response_uri. The verification
service decrypts it, verifies the credential and holder proof, and binds the result
to this transaction. Only a successful trusted result can lead to an OIDC code.
The JWS signing chain, issuer trust anchors, and response-encryption key have
separate roles; none replaces the others.
11.3 Age request: European Age Verification Blueprint
Try the age verification example — CORE #4.
It illustrates the av:// flow below, requesting only age_over_18 through
proof_of_age, without client_metadata.
The age flow follows the mdoc OID4VP fallback in the European Commission's
Age Verification Blueprint — Annex A, AV Profile.
It uses av://, unsigned URL query parameters, a redirect_uri: client identifier,
vp_token, DCQL and direct_post. There is no request or request_uri in this
flow. It is the dedicated CORE age path, separate from the HAIP identity path.
The current blueprint also specifies Digital Credentials API / zero-knowledge presentation mechanisms. This bridge path implements the mdoc OID4VP fallback; it does not implement those mechanisms or establish full blueprint conformity. See also the European Commission overview and the France Identité age integration guide.
The reference test sends the following DCQL query. The dedicated bridge verifier
uses the same selection and additionally makes
require_cryptographic_holder_binding: true explicit; this is also the OID4VP
default when the property is omitted. Both require a valid holder proof:
{
"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"
],
"intent_to_retain": false
}
]
}
]
}
This illustrative Python example produces the URL-encoded invocation with that query. Its example endpoint and nonce are placeholders, not a live transaction:
import json
from urllib.parse import urlencode
response_uri = "https://verifier.example/verifier/age/response/EXAMPLE_HANDLE"
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"],
"intent_to_retain": False,
}],
}],
}
invocation = "av://?" + urlencode({
"client_id": "redirect_uri:" + response_uri,
"response_type": "vp_token",
"response_mode": "direct_post",
"response_uri": response_uri,
"nonce": "EXAMPLE_FRESH_NONCE",
"dcql_query": json.dumps(dcql_query, separators=(",", ":")),
})
print(invocation)
Like CORE #4, this example has no client_metadata. The dedicated bridge
verifier may advertise supported credential formats when configured to include
metadata; this does not add encryption to direct_post. It has no response-encryption
JWK or encrypted-response algorithm list. The Wallet posts vp_token to response_uri. Although the request is
unsigned and the response uses direct_post, the attestation and device proof
remain cryptographically verified: issuer trust, signature, digests, validity,
holder binding and transaction transcript must all pass. Only boolean true is
accepted for age_over_18; identity attributes are not requested or released.
11.4 OIDC attribute mapping and subject continuity
The service can use the France Identité compatibility profile for an identity
client. Your application still uses the same OIDC endpoints and validation rules. This policy handles the observed mdoc response using DCQL query
pid_mdoc and namespace attribute birth_place as text, separately from the
current Rulebook policy. It requires a completed, transaction-bound verification service result,
established issuer trust and strict verification service verification. A signer subject name
alone is never sufficient; no bridge certificate allowlist is introduced.
For this policy only, the outgoing bridge-specific place_of_birth claim is a
string when authorized under profile. For example, a fictitious value could
be "DEMO CITY". It is not converted into locality, country or region.
Clients consuming the claim must handle a string for this explicitly selected
policy or an object for the Rulebook policy. The ID Token and UserInfo use the
same value and type. Clients not authorized for birthplace do not receive it.
Identity and age verification use different attestations and presentation flows. Validate each flow with the wallet versions and trust environment used by your application.
11.5 How we derive the subject identifier (sub)
Treat sub as an opaque value supplied by the service. Store (iss, sub)
as the external account key; do not calculate an identifier from profile claims.
The service matches verified given name, family name, birth date and birthplace,
then returns a persistent random identifier for the callback hostname's sector.
Email, nationality and the credential's signing certificate are not identity inputs.
For France Identité, birthplace remains opaque text. The Rulebook policy uses a
structured birthplace. Changing between these representations can change sub.
Clients on the same callback hostname can receive the same sub; registering a
new Client ID on that hostname does not create a separate identity sector.
Plan account linking and recovery around these limits:
- Two people with identical matching attributes can receive the same subject identifier. This is not a guaranteed unique legal-person identifier.
- Changes in name, spelling or birthplace can change
sub. Credential renewal alone does not guarantee continuity. A new signing certificate is not, by itself, an identity change. - Changing the callback hostname, issuer or provider identity policy can change the account key. Coordinate such changes with the provider before rollout.
- If a new key must be linked to an existing account, authenticate that local account and require explicit authorization. Never merge or recover accounts automatically by name, birth date or email.
For age verification, sub is transaction-specific and ephemeral. Use the
verified age result for the requested access decision, never for persistent
account matching.
12 Privacy and data retention
We request the minimum attributes for the selected purpose: four verified
identity attributes for identity login, or only age_over_18 for age verification.
This section states the current technical storage behaviour. Our published
Privacy Policy and
Terms remain the reference notices. Encryption and
pseudonymization do not make personal data anonymous.
12.1 What the bridge retains
| Data | Storage and purpose | Retention / deletion |
|---|---|---|
Identity lookup aliases, internal random subject IDs, sector-specific sub associations |
Persistent bridge database, for account continuity; no raw name, birth date or birthplace in this registry | No automatic expiry or self-service identity deletion is implemented. Revoking or archiving a client does not erase this registry. |
| Application registration | Client ID, public name, exact callbacks, scopes/claims, policy, sector, status/revision, creation/archive dates and free-client registration record | Persistent; archiving disables access and reserves the Client ID rather than deleting the registration row. No automatic retention deadline. |
| Client secret | Only a password hash is stored in the database; the plaintext is returned once in the successful registration/rotation POST response | The secret screen clears after five minutes or Close. This does not revoke the client. Rotation replaces the hash; archiving clears it. The developer must protect any saved copy. |
| Developer contact | Company, first/last name, verified email, verification date, acknowledgement date and policy URLs, encrypted in bridge storage; accessible to authorized client administrators | Kept with the client, without automatic expiry; the contact row is removed when the client is archived. Separate from an end user's identity/email. |
| Administrative audit | Actor reference, client ID, action, revision and timestamp | Persistent; no automatic purge is implemented. It contains neither client secrets nor Wallet identity values. |
| Optional verified end-user contact email | Encrypted email bound to the pairwise sub, plus verified_at; only for authorized identity clients requesting scope email |
Reusable for 30 days from verification, without extending that date on reuse. Expired data is unusable; physical deletion requires the provider's scheduled purge to run. Controlled deletion can remove the email without changing sub. |
The bridge does not persist a full PID or raw identity attributes in its identity registry. It does temporarily process authorized claims for the OIDC exchange. This distinction also applies to an age result: no persistent identity subject is created for it, but its short-lived transaction and tokens still exist.
12.2 Temporary browser and protocol state
Protocol records are encrypted in the service’s temporary storage and have TTLs. Counters and concurrency leases also have expirations, but are not identity records. A browser cookie binds the browser to its own transactions; the signup secret is never placed in that cookie.
| Temporary data | Current lifetime |
|---|---|
| Wallet transaction and authorized claims | Up to 300 seconds for identity-only or age verification; the optional email step can add up to 600 seconds after identity verification |
| OIDC authorization code and associated claims | 60 seconds, single-use |
| Access-token state for UserInfo | 300 seconds; no refresh token |
| Nonce replay marker | 600 seconds |
| Browser-owned bridge diagnostic download | 600 seconds from the last recorded diagnostic event |
| Pending developer registration, including contact fields | Up to 30 minutes; consumed on successful registration or cancelled through the form |
| Email verification challenge | Code valid five minutes, at most five attempts; only a keyed digest is stored in the encrypted challenge, with at least 60 seconds between sends |
| Rate-limit counters | Depending on the endpoint: 60 seconds, 15 minutes, one hour or 24 hours from the first increment; daily quota buckets change at midnight UTC |
| Concurrency bookkeeping | Tracking records expire after at most 900 seconds from their last update; expired transaction leases no longer count as active |
Expiry prevents further protocol use; it does not invalidate copies already received by a client or erase storage backups. Closing the secret screen or a browser tab does not itself revoke a client or erase all transaction state. The application receives identity claims only within its authorized scopes and claims, and owns its session, account and token-retention policy. Do not store ID Tokens indefinitely or use email as an automatic account-recovery key.
12.3 Verification service, diagnostics and infrastructure
Our verification service has separate storage. It retains normalized
presentation results, including disclosed claims and verification outcomes,
in its database. Transaction expiry does not delete these results.
This includes the four disclosed identity fields, or the disclosed age result
for an age presentation. Consequently, bridge transaction lifetimes and
intent_to_retain: false must not be read as a guarantee that every component
automatically erases the presentation after five minutes. There is no automatic
deletion deadline for this persistent verification history. For retention or
deletion requirements, contact us; do not base your
own privacy notice on the lifetime of the bridge transaction alone.
The downloadable bridge diagnostics contain stages, statuses, attribute presence/types and technical correlation references, never identity values, email addresses, codes, tokens, nonce, PKCE verifier, cookies or secrets. Verification-service traces are separate and follow their configured retention and redaction settings; they are not covered by the bridge's ten-minute trace TTL.
Web-server access logs, SMTP delivery records and database/Redis backups are additional infrastructure records. Their retention is configured separately; they are not covered by the transaction or diagnostic expiry times above. IP addresses are used for abuse controls, with hashed Redis key identifiers. Do not enable proxy logging of callback query strings, POST bodies, tokens or secrets. SMTP providers process the recipient address and verification message; that is separate from a Wallet identity attestation.
For an account or data-deletion request, use the contact details in our published Privacy Policy. There is currently no public API for deleting an identity or its verification history. Client revocation, contact-email deletion and deletion of an application's local user account are different operations; none alone promises deletion across all services and backups.
12.4 Hosting and security
We host the public bridge on AWS EC2 in the Europe (Paris) region,
eu-west-3, in France, within the European Union. AWS identifies this region
in its official region list.
The public endpoint is https://login.openid4vc-hub.com.
The hosted service uses HTTPS, encrypted temporary protocol payloads, encrypted contact data and hashed client secrets. Signing and encryption keys remain with the provider. Your integration uses the public OIDC endpoints and its own client credentials; protect your callback, server-side tokens and application sessions.
This location statement covers our public bridge hosting. SMTP delivery, application-side data, logs and backup copies have their own processing and retention arrangements, described above; their location is not established by the bridge's AWS region alone. Hosting in Europe does not change which identity attributes are requested, the application's authorized claims or the retention limits described in this guide.
Automated Free registration (JSON)
GET/POST /register/api exposes the same onboarding engine as /register.
This is a proprietary, session-bound JSON API, not OpenID Dynamic Client
Registration and not a Hub Application API endpoint. It is available only when
public self-service registration is enabled. HTTPS is required; CORS is not
provided. Use a private HTTP cookie jar per developer registration and retain it
throughout the exchange. No admin token or Hub API key is required.
GET /register/apireturnscsrfandstatus: ready(or the current pending stage), and establishes the session cookie.POST /register/apiwithContent-Type: application/jsonstarts registration:
json
{
"csrf": "TOKEN_FROM_GET", "step": "create",
"name": "My application", "callback": "https://app.company.org/callback",
"company": "My company", "first_name": "Alice", "last_name": "Developer",
"developer_email": "alice@company.org", "read_policies": "on",
"purpose": "pid", "email": "on"
}
The developer must acknowledge https://talao.io/cgu.html and
https://talao.io/privacy.html before submitting read_policies: "on".
purpose is required: pid or age_verification. Omit email unless the
separately verified user contact email is needed for PID. All input values
are strings; unknown fields (including quota, policy and scope overrides)
are rejected. This API registers confidential web clients with one exact
public HTTPS callback and client_secret_basic; native MCP clients continue
using /connect. A successful start returns HTTP 200 with status: email
and expires_at (Unix seconds), and sends the developer a code.
3. Submit {"csrf":"TOKEN_FROM_GET","step":"email","code":"123456"}
with the same cookie jar. A correct code returns HTTP 201, status: created,
quota_profile: free, a new csrf, and configuration containing issuer,
client_id, client_secret, redirect_uri, token_endpoint_auth_method
and scope. Save this configuration privately: it has the same fields as
the plugin JSON download. Only this successful POST returns the secret;
GET and retries never recover it. A lost response requires operator rotation.
step: resend requests another code; step: cancel discards the pending
registration. Both require the same session and CSRF. Email codes last five
minutes with five attempts and a 60-second resend cooldown. Pending registration
expires after 30 minutes. API and original form share pending state, browser/IP,
email and creation limits; /connect keeps its distinct pending state while
sharing anti-abuse budgets. Never create a new registration to retry a lost
successful response automatically.
New clients receive the Free plan: default 100 verification attempts per UTC calendar month and 10 per UTC day and three concurrent sign-ins shared by callback hostname, 60 starts/minute/client and 10/minute/IP/client. The service enforces the effective limits; downloaded configuration does not contain quota settings. Quota changes and self-service disabling do not reset consumption. No Pro/Custom selection is accepted.
Errors: 400 for invalid input/session/code, 404 when registration is disabled, 413 for oversized requests, 415 for non-JSON POST, 429 for rate limits and 503 for temporary storage unavailability. Normal validation, rate-limit and storage errors are JSON; infrastructure/framework errors may be HTML. All responses are no-store. The current generic 429 Retry-After is not the real quota reset interval. No public recovery, rotation or revocation API is introduced.
Quota windows and plan changes
Daily limits reset at midnight UTC; monthly limits reset at 00:00 UTC on the
first day of the calendar month. No carryover or automatic overage billing.
A verification attempt is a valid authorization start admitted by the daily and
monthly budgets. Failed, refused, expired and abandoned sessions are retained
in consumption; an API poll, Token call or status check does not create another
attempt. Minute-limit, concurrency and daily/monthly-limit refusals do not count.
At a daily/monthly limit, new starts return HTTP 429; active sessions continue.
Applications receive the existing safe OIDC temporarily_unavailable response.
Free uses shared callback-hostname budgets (native MCP: verified developer email). Pro and Custom use per-client budgets. Each client is tracked individually in all modes so switching plans cannot clear its usage; on Free, both its own and the shared Free budgets apply. Plan changes never rotate credentials. Pro activation, downgrade, cancellation and monthly commercial arrangements remain manual via the provider until online billing is enabled. When the pricing page shows Subscribe to Pro, eligible clients can confirm their existing developer email, pay on Stripe and manage renewal/cancellation in its customer portal. Pro is activated only after server-side payment confirmation. This optional workflow excludes Shopify and Custom clients; availability depends on provider activation. Billing and UTC calendar-month verification quotas have separate periods, and changing a plan does not reset consumption. Contact https://wallet-connectors.com/pricing/ for Pro or Custom.
For usage questions or plan changes, contact the provider with your Client ID; never send your client secret. Quotas are enforced by the service and are absent from the downloaded client JSON. Updating a plugin or importing that JSON again does not change the plan or reset consumption.
13 Conformance testing and evidence
The bridge delegates PID credential verification to the Hub through its HTTP Application API. Testing the credential verifier is separate from testing the bridge's OIDC callback, PKCE, Token and UserInfo flow.
Download the strict-mode Verifier test evidence ZIP — 2 November 2026, including a French scenario report, sanitized JSON results, the three uploaded screenshots and SHA-256 checksums. This is a self-run test record, not an official OpenID certification or proof of full HAIP conformance. Checksums verify integrity, not independent attestation.
13.1 Strict-mode scenario and results
The campaign used suite 5.3.2, revision 11999aa, plan
xvw8LcXV2e6Jy,
with the OID4VP 1.0 Final + HAIP 1.0 Final Verifier profile: iso_mdl,
direct_post.jwt, x509_hash and signed Request Objects fetched by reference.
The target was haip-verifier-2 / eu-pid-mdoc, exposed through
sandbox HAIP #6, using
eu.europa.ec.eudi.pid.1 with structured place_of_birth. The operator switched
the Verifier to strict mode and reported adding the Foundation's test IACA
before the run. Each positive result confirms trust_mode=strict and
issuer_trust=x509_trust_anchor. No trust setting was changed by the test runner.
A fresh presentation was generated for each module. The suite acted as a simulated Wallet and submitted a fictitious mdoc. The sandbox observed the Hub result through polling; the same-browser confirmation checkbox was left off. All three positive browser resume links displayed Success. Their screenshots were attached to the suite, and each module finished before the next started.
| Module | Final suite result | Hub observation |
|---|---|---|
| Happy flow | REVIEW | Completed; strict trust, anchored issuer |
| Request URI POST | REVIEW | Completed; strict trust, anchored issuer |
| Request URI fetched twice | REVIEW | Completed; strict trust, anchored issuer |
| Invalid SessionTranscript | PASSED | Invalid DeviceSignature rejected as expected |
All four modules are FINISHED, with no FAILURE or WARNING events in the consulted logs. REVIEW is pending manual evidence review, not PASSED. The negative test reached the intended signature rejection, rather than a prior missing-claim or issuer-trust error.
13.2 Scope, history and limitations
These four tests establish the observed results in strict mode for this target.
They do not exhaustively test every untrusted issuer or certificate path. The
Hub reports certificate revocation and credential status as not checked in
these sessions. The proofs disclose nationality in addition to name, birth
date and structured birthplace; this is not evidence of the bridge's
four-attribute disclosure policy.
This is a simulated Wallet campaign, not a test of the France Identité mobile
application, its textual birth_place compatibility presentation or every
European Wallet. It does not test the complete bridge OIDC flow or its dedicated
Verifier haip-bridge-verifier.
The earlier same-target plan
1rIKgauNLzMcN
was run in insecure_test mode (three REVIEW, one PASSED). Its dated source
folder and ZIP remain separate in the repository; the historical results have
not been relabelled as strict. Its POST browser-return issue was not observed
in the new strict campaign.
An earlier, distinct
bridge-targeted campaign
returned three FAILED and one PASSED: the AWS bridge-pid-mdoc presentation
requested legacy birth_place and nationality, and the positive mock proofs
lacked birth_place. The successful sandbox #6 strict campaign does not resolve
failures on that other target. Preserve the separate France Identité profile.
The OpenID Foundation notes that its OID4VP/HAIP tests do not exhaustively test mdoc or credential-specific requirements. See the official testing and certification instructions.
13.3 Separate SD-JWT campaign — sandbox HAIP #2
Download the strict SD-JWT evidence ZIP
— 2 November 2026. This separate
plan MWcJv5OG8jV3R
tests haip-verifier-2 / pid-sd-jwt, available through
sandbox HAIP #2.
It uses the same suite version and profile as above, with the sd_jwt_vc
variant, direct_post.jwt, x509_hash and signed Request Objects by reference.
After the operator confirmed the strict setting, all eleven scenarios were
started from the beginning in fresh sessions. The first exploratory plan
QZKC5DzprEVt1 contains an earlier insecure_test happy-flow and remains
historical; it is not counted in the strict results.
| Scenarios | Suite result | Hub observation |
|---|---|---|
| Happy flow, minimal cnf JWK, Request URI POST, Request URI fetched twice | 4 REVIEW | Completed in strict mode, with successful browser resumes |
| Invalid KB-JWT signature and invalid credential signature | 2 PASSED | Respective signature rejected |
| Invalid sd_hash, nonce and audience | 3 PASSED | Respective binding rejected |
| KB-JWT iat too old or in the future | 2 PASSED | Issued-at outside the allowed window rejected |
11 FINISHED: 7 PASSED, 4 REVIEW, with four attached positive screenshots,
no pending image uploads and no FAILURE or WARNING events in the consulted logs.
The four positive results confirm trust_mode=strict and
issuer_trust=local_signing_key: the SD-JWT issuer is trusted through an
active local Hub signing key. This does not demonstrate an SD-JWT certificate
path through the mdoc IACA. Do not interchange credential trust purposes.
The fictitious PID discloses given_name, family_name and birthdate; values
are excluded from the sanitized report. The suite simulates the Wallet, the
sandbox polls the result and all four positive Hub resume pages displayed
Success. The seven negatives returned invalid_vp_token with the intended
signature, binding or time-window diagnostics, rather than an unrelated trust
or missing-claim rejection.
REVIEW remains manual review, not certification. The campaign does not establish certificate revocation, credential status checking, arbitrary issuer-path support, a real mobile Wallet exchange or the complete bridge OIDC flow. The mdoc evidence and the unresolved dedicated bridge-target campaign remain separate. The SD-JWT ZIP includes its scenario, filtered results, screenshots and checksums; no keys, private configuration, tokens or raw logs are distributed.