Reusable core library on NuGet: dotnet add package miEUDIverifier.Core
A C#/.NET 8 ASP.NET Core web app (Minimal API with a browser UI) that talks to the EUDI Verifier Backend REST API (OpenID4VP) to read identity data from an EUDI Wallet.
This verifier reads only the PID credential (Personal Identification Data) and only the following three attributes:
| Field | DCQL path (mso_mdoc) | DCQL path (sd-jwt) |
|---|---|---|
| Family name | eu.europa.ec.eudi.pid.1 / family_name |
family_name |
| Given name | eu.europa.ec.eudi.pid.1 / given_name |
given_name |
| Date of birth | eu.europa.ec.eudi.pid.1 / birth_date |
birth_date |
Note: The application can of course be extended to request more attributes (or other credential types). The requested claims are defined in the DCQL query built in
VerifierApiService– just add the desired paths there and map the returned values inExtractIdentityDataAsync.
┌─────────────┐ POST /ui/presentations ┌──────────────────────┐
│ Web app │ ──────────────────────────────► │ EUDI Verifier │
│ (Verifier) │ ◄────────────────────────────── │ Backend │
│ │ transaction_id + request_uri │ (verifier.eudiw.dev │
│ Show QR │ │ or local) │
│ code in │ └──────────────────────┘
│ browser │ ▲
│ │ │ VP Token
└─────────────┘ │
▲ ┌──────────┴───────┐
│ GET /ui/presentations/{id} │ EUDI Wallet App │
│ (auto-polling) │ (iOS / Android) │
│ │ scans QR code │
└───────────────────────────────────────┴──────────────────┘
The app starts a local web server, automatically opens the browser and shows the QR code as an image. After the scan, the page polls the wallet's response server-side and displays the identity data – no manual reload needed.
A public test instance is available at https://miEUDIverifier.mitterbucher.com — just open it and click New request to get a fresh QR code, then scan it with your EUDI Wallet App.
No guarantee of availability — this endpoint may be offline at any time. To run your own instance, see the Quick start below.
- .NET 8 SDK
- Internet access to
https://verifier.eudiw.devor Docker (for local operation) - EUDI Wallet App (Android / iOS) holding an issued PID credential – see the next section
For the verifier to read anything, the wallet app must be installed and must hold a PID credential (Personal Identification Data). In the public demo environment this works entirely with test data – no real ID document required.
- Android: Pre-built APKs are available as release assets:
→ eudi-app-android-wallet-ui / releases
Download the APK of the latest
*-Demoversion and sideload it ("Install from unknown sources" must be allowed). When switching versions a clean reinstall may be required (uninstall the previous version first). - iOS: Source code and build instructions are at eudi-app-ios-wallet-ui (no public App Store build – build it yourself or use a provided TestFlight invitation).
The public test environment issuer.eudiw.dev issues PID credentials with test data:
- Open the Credential Offer page in your browser: issuer.eudiw.dev/credential_offer
- Select PID in the MSO Mdoc format and generate the offer → a QR code / barcode (or a deep link) appears.
- In the EUDI Wallet App go to Documents → Add document / scan QR and scan the barcode.
- Follow the test environment's authentication and confirmation steps and accept the offer with Add.
Afterwards the PID credential is stored in the wallet and can be read by this verifier.
Note: This verifier requests the PID in both formats — mso_mdoc and SD-JWT VC (
dc+sd-jwt) — as alternative options, so the wallet can present whichever it holds. On the test issuer you can therefore load PID (MSO Mdoc) or PID (SD-JWT VC); both work.In addition, the German EUDI Wallet (EUDIWalletDE, Bundesdruckerei prototype) is supported as a further option: its PID uses a different credential type (
https://demo.pid-issuer.bundesdruckerei.de/credentials/pid/1.0) and the OIDC-style claim namebirthdate, which this verifier requests as a separate DCQL alternative (configurable viaGermanPidVctValues). Note that the wallet must also trust the verifier backend — with the public eudiw.dev demo backend this depends on the wallet's trust settings.
# 1. Clone the repository / unpack the project
cd miEUDIverifier
# 2. Restore dependencies and run
dotnet run --project src/miEUDIverifier
# → The browser opens automatically (http://localhost:5050)
# 3. Scan the QR code in the browser with the EUDI Wallet App
# → The app asks for consent to share the data
# → After confirmation the web page shows name and date of birthBesides the demo page at /, the app exposes a session-based REST API so external
applications can run any number of verifications in parallel. Each POST /api/verification
creates an independent session with its own QR code, polling and result. The demo page is
unaffected.
| Method & path | Description |
|---|---|
POST /api/verification |
Start a verification. Returns 201 with { id, status, deepLink }. |
GET /api/verification/{id}/qrcode |
QR code as a PNG image (image/png). |
GET /api/verification/{id}/status |
Current status: waiting / complete / partial / error. |
GET /api/verification/{id}/data |
Extracted PID data once available (200); 409 while still waiting or on error. |
DELETE /api/verification/{id} |
Discard the session and cancel its polling (204). |
Unknown session ids return 404. Sessions are evicted automatically after
SessionTtlMinutes (default 30).
A verifier backend signs every request object with one access certificate for one trust ecosystem, so different wallets need different backend instances (see below). The app can serve several from one deployment via named backends:
| Call | Behaviour |
|---|---|
GET /api/backends |
Lists the configured backend keys and the default. |
POST /api/verification?backend=<key> |
Starts the verification against that backend. Unknown/unconfigured key → 400. |
POST /api/reset?backend=<key> |
Re-targets the current demo session to another backend. |
GET /?backend=<key> |
Demo page uses that backend for the new browser session. |
The chosen backend is echoed back as backend in the status/data responses. Configure the
backends via environment (fluent with the EUDI_ prefix):
EUDI_VerifierSettings__Backends__eu="https://verifier-backend.eudiw.dev" # EUDI reference wallet
EUDI_VerifierSettings__Backends__de="http://eudi-verifier-backend-de:8080" # German EUDI Wallet (own backend)
EUDI_VerifierSettings__DefaultBackend="eu"When Backends is not set, the single BackendUrl is used as the eu backend (unchanged default).
Why more than one backend? The German EUDI Wallet (EUDIWalletDE) only trusts a verifier whose certificate comes from the German Relying-Party Access CA (obtained via the SPRIND sandbox), while the EUDI reference wallet trusts the eudiw.dev reference CA. One instance = one signing certificate = one ecosystem. A ready-to-fill template for the German backend instance is in
docker/docker-compose.de-backend.yml— drop in the SPRIND-issued.p12and setBackends__de. Until then, onlyeuis active.Full background — trust model, why each wallet does or doesn't work, the SPRIND onboarding path and the multi-backend design — is documented in docs/trust-and-multi-backend.md.
BASE=http://localhost:5050
# 1. Start a verification → returns a session id
ID=$(curl -s -X POST $BASE/api/verification | jq -r .id)
# 2. Show the QR code to the user (PNG) — or render the returned deepLink yourself
curl -s $BASE/api/verification/$ID/qrcode -o qr.png
# 3. Poll the status until it is "complete"
curl -s $BASE/api/verification/$ID/status
# {"id":"…","status":"waiting","error":null}
# 4. Fetch the PID data once complete
curl -s $BASE/api/verification/$ID/data
# {"id":"…","status":"complete","familyName":"…","givenName":"…",
# "birthDate":"1967-06-10","format":"mso_mdoc"}
# 5. Clean up (optional — otherwise the session expires after the TTL)
curl -s -X DELETE $BASE/api/verification/$IDAll settings live in src/miEUDIverifier/appsettings.json:
| Key | Default | Description |
|---|---|---|
BackendUrl |
https://verifier.eudiw.dev |
URL of the verifier backend |
PollIntervalSeconds |
3 |
Polling interval (seconds) |
PollTimeoutSeconds |
120 |
Maximum wait time |
Profile |
openid4vp |
OpenID4VP profile (openid4vp or haip) |
AuthorizationRequestScheme |
openid4vp |
URI scheme for the QR code |
IssuerChain |
EUDI demo CA | PEM certificate of the trusted PID issuer |
SessionTtlMinutes |
30 |
Time-to-live for REST-API verification sessions |
Override via environment variable (prefix EUDI_):
EUDI_VerifierSettings__BackendUrl=http://localhost:8080 dotnet run --project src/miEUDIverifierOr via CLI argument:
dotnet run --project src/miEUDIverifier -- --VerifierSettings:BackendUrl=http://localhost:8080The address and port the built-in web server (Kestrel) listens on are controlled via the
Kestrel:Endpoints section in src/miEUDIverifier/appsettings.json. By default only HTTP is
active:
The Url value determines the binding and port. On startup the detected local and LAN addresses
are printed to the console.
Example Url |
Meaning |
|---|---|
http://0.0.0.0:5050 |
All network interfaces (also reachable on the LAN) – default |
http://localhost:5050 |
Local machine only |
http://192.168.1.42:5050 |
Bind to a specific IP address |
http://my-hostname:5050 |
Bind to a specific hostname |
Add an Https endpoint with a certificate. Kestrel supports two certificate formats:
A) PFX/PKCS#12 (certificate + key in a single file):
"Kestrel": {
"Endpoints": {
"Http": { "Url": "http://0.0.0.0:5050" },
"Https": {
"Url": "https://0.0.0.0:5443",
"Certificate": {
"Path": "certs/my-certificate.pfx",
"Password": "<do-not-put-it-here-see-below>"
}
}
}
}B) PEM (separate certificate and key files):
"Https": {
"Url": "https://0.0.0.0:5443",
"Certificate": {
"Path": "certs/fullchain.pem",
"KeyPath": "certs/privkey.pem"
}
}If an HTTPS endpoint is configured, the app automatically opens the local HTTPS address
(https://localhost:<port>) on startup.
The password does not belong in the committed appsettings.json. Instead:
# Via environment variable (prefix EUDI_, __ separates the levels):
EUDI_Kestrel__Endpoints__Https__Certificate__Password=secret dotnet run --project src/miEUDIverifier
# Or via user secrets (local only, never written to the repo):
dotnet user-secrets --project src/miEUDIverifier set "Kestrel:Endpoints:Https:Certificate:Password" "secret"Note:
*.pfx,*.pem,*.key,*.crtas well as theCert/folder are already excluded in.gitignore/.dockerignore– so certificates won't accidentally end up in the repository or Docker image.
For local HTTPS without your own certificate:
dotnet dev-certs https -ep certs/dev.pfx -p devpass
dotnet dev-certs https --trust # mark the certificate as trusted in the OSMount the certificate into the container as a volume, publish the port and pass the password as an environment variable – never copy it into the image:
docker run --rm \
-p 5443:5443 \
-v "$PWD/certs:/app/certs:ro" \
-e EUDI_Kestrel__Endpoints__Https__Url="https://0.0.0.0:5443" \
-e EUDI_Kestrel__Endpoints__Https__Certificate__Path="certs/my-certificate.pfx" \
-e EUDI_Kestrel__Endpoints__Https__Certificate__Password="secret" \
mieudiverifier:latestFor development without internet access or for full control:
cd docker
docker compose up -d
# Then start the app pointing at the local URL:
EUDI_VerifierSettings__BackendUrl=http://localhost:8080 dotnet run --project src/miEUDIverifierNote: With a local backend the wallet app must be able to reach the backend. Replace
localhostwith your machine's LAN IP and setVERIFIER_PUBLICURLindocker/docker-compose.ymlaccordingly.
miEUDIverifier/
├── miEUDIverifier.sln
├── README.md
├── Dockerfile # Container build of the web app
├── docker/
│ └── docker-compose.yml # Local EUDI verifier backend stack + app
└── src/
├── miEUDIverifier/ # ASP.NET Core web app (Minimal API)
│ ├── miEUDIverifier.csproj
│ ├── appsettings.json # Config: backend, Kestrel/HTTPS, logging
│ ├── Program.cs # Entry point, endpoints & flow control
│ └── WebServer/
│ ├── AppState.cs # Runtime state (transaction, polling)
│ └── HtmlPage.cs # Single-page browser UI (HTML)
├── miEUDIverifier.Core/ # Reusable library
│ ├── miEUDIverifier.Core.csproj
│ ├── MiEUDIverifierExtensions.cs # AddMiEUDIverifier() DI extension
│ ├── Configuration/
│ │ └── VerifierSettings.cs # Configuration model
│ ├── Models/
│ │ ├── InitTransactionRequest.cs # DCQL request
│ │ ├── InitTransactionResponse.cs # Response with transaction_id
│ │ └── WalletResponseModels.cs # VP response & IdentityData
│ └── Services/
│ ├── VerifierApiService.cs # REST API client (core logic)
│ └── QrCodeService.cs # QR code output (PNG)
└── miEUDIverifier.Core.Tests/ # xUnit tests (offline)
| Standard | Description |
|---|---|
| OpenID4VP 1.0 | Presentation protocol |
| DCQL | Digital Credentials Query Language |
| ISO/IEC 18013-5 | mDL / mDoc data format |
| SD-JWT VC | Selective Disclosure JWT Verifiable Credentials |
| EUDI ARF | EU Digital Identity Architecture |
- eudi-srv-verifier-endpoint – Verifier backend (Kotlin/Spring Boot)
- eudi-web-verifier – Verifier frontend
- eudi-app-android-wallet-ui – Android wallet app
- eudi-app-ios-wallet-ui – iOS wallet app
- eudi-srv-pid-issuer – PID issuer
Licensed under the Apache License, Version 2.0 – in line with the EUDI Reference Implementation. See LICENSE and NOTICE.
Copyright 2026 Wolfgang Mitterbucher.
All third-party dependencies are permissive (MIT / Apache 2.0) and compatible with this license.
Wolfgang Mitterbucher — Software Engineering & Digital Identity, Leonding (Austria)
🌐 www.mitterbucher.com · 💼 LinkedIn · ✉️ office@mitterbucher.com
More open-source projects: miPDFconvert · miPDFvalidator · miEUDIverifier
{ "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5050" } } } }