OpenID Connect · OIDC4VP · Developer integration

Bridge OIDC - EUDI Wallet

Connect an EUDI Wallet with OpenID Connect.

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.

  1. 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.
  2. 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.
  3. 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.
  4. Confirm that you have read the linked terms of use and privacy policy, then request the email code.
  5. 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.
  6. After successful email verification, the service creates the client and generates its client_id and 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:

  1. state to bind the callback to the initiating browser transaction;
  2. nonce to bind the ID Token to that authentication request;
  3. code_verifier, a high-entropy string of 43–128 characters from A–Z a–z 0–9 - . _ ~;
  4. 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):

  1. Copy your return URL with Copy URL.
  2. Select Register my site to open the registration form in a new tab. Paste that exact URL into the return URL (redirect_uri) field.
  3. Complete the form and verify your developer email using the received code.
  4. On the confirmation page, select Download configuration (JSON). Keep the file confidential: it contains the client secret.
  5. 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
  1. 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.
  2. 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.
  3. Before configuring the connection, open free client registration. Select Identity verification and register that exact callback. Authorize openid profile email and 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

  1. 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.
  2. Select Sign in with your EUDI Wallet (Se connecter avec mon EUDI Wallet).
  3. Present the requested identity in the Wallet and authorize the presentation.
  4. 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.
  5. 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.

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.

  1. 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.
  2. Open Preferences → Link my EUDI Wallet (Préférences → Lier mon Wallet EUDI). The linking page is /auth/eudiwallet/link.php on your Moodle installation, for example https://moodle.example/auth/eudiwallet/link.php.
  3. 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.
  4. Present your identity with the Wallet. Complete the bridge's separate contact email verification if requested, then return in the same signed-in browser.
  5. Moodle displays Your Wallet is linked to this Moodle account (Votre Wallet est lié à ce compte Moodle) and returns to Preferences.
  6. 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:

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
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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
  1. The student signs in using their Wallet or an existing Moodle login, enrols in the course and completes the lessons and final assessment.
  2. 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.
  3. 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_code entirely: there is no separate transaction code to enter. Keep the QR and link private.
  4. 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.
  5. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. The customer signs in to their Shopify customer account and adds a restricted product to the cart. Checkout requires age verification.
  2. They select the EUDI Wallet button, open a compatible wallet and approve the request for proof that they are 18 or over.
  3. 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

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.1 link from a cloud runtime does not point to the user's computer.
  3. 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.
  4. The user reviews the request in their wallet and authorizes presentation. The agent must not operate the wallet on their behalf.
  5. The agent calls get_verification_status with the original identifier, no more frequently than every three seconds. Only verified together with age_over_18: true confirms 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.

  1. 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.
  2. Read the notice and tick the checkbox before sending a message to OpenAI. Do not enter personal or confidential information in the conversation.
  3. For an adult event, ask to verify your age. The assistant calls start_age_verification; the page displays a verification button.
  4. 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.
  5. 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 verified together with age_over_18: true unlocks 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:

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.

  1. GET /register/api returns csrf and status: ready (or the current pending stage), and establishes the session cookie.
  2. POST /register/api with Content-Type: application/json starts 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.