Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

miEUDIverifier

NuGet NuGet downloads License Docker on GHCR

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 in ExtractIdentityDataAsync.

Flow (Cross-Device)

┌─────────────┐     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.

Try it (test endpoint)

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.

Prerequisites

  • .NET 8 SDK
  • Internet access to https://verifier.eudiw.dev or Docker (for local operation)
  • EUDI Wallet App (Android / iOS) holding an issued PID credential – see the next section

Set up the EUDI Wallet App & a test PID

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.

1. Install the wallet app

  • Android: Pre-built APKs are available as release assets: → eudi-app-android-wallet-ui / releases Download the APK of the latest *-Demo version 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).

2. Load a test PID into the wallet

The public test environment issuer.eudiw.dev issues PID credentials with test data:

  1. Open the Credential Offer page in your browser: issuer.eudiw.dev/credential_offer
  2. Select PID in the MSO Mdoc format and generate the offer → a QR code / barcode (or a deep link) appears.
  3. In the EUDI Wallet App go to DocumentsAdd document / scan QR and scan the barcode.
  4. 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 name birthdate, which this verifier requests as a separate DCQL alternative (configurable via GermanPidVctValues). 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.

Quick start

# 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 birth

REST API

Besides 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).

Multiple trust ecosystems (?backend=)

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 .p12 and set Backends__de. Until then, only eu is 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.

Example

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/$ID

Configuration

All 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/miEUDIverifier

Or via CLI argument:

dotnet run --project src/miEUDIverifier -- --VerifierSettings:BackendUrl=http://localhost:8080

Hostname, IP address & HTTPS/SSL

The 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:

{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://0.0.0.0:5050"
      }
    }
  }
}

Hostname / IP address / port

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

Enable HTTPS / SSL

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.

Do NOT store the certificate password in clear text

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, *.crt as well as the Cert/ folder are already excluded in .gitignore/.dockerignore – so certificates won't accidentally end up in the repository or Docker image.

Create a development certificate (optional)

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 OS

Docker

Mount 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:latest

Local verifier backend (Docker)

For 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/miEUDIverifier

Note: With a local backend the wallet app must be able to reach the backend. Replace localhost with your machine's LAN IP and set VERIFIER_PUBLICURL in docker/docker-compose.yml accordingly.

Project structure

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)

Standards & protocols used

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

Referenced EUDI repositories

License

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.


Author

Wolfgang Mitterbucher — Software Engineering & Digital Identity, Leonding (Austria)

🌐 www.mitterbucher.com · 💼 LinkedIn · ✉️ office@mitterbucher.com

More open-source projects: miPDFconvert · miPDFvalidator · miEUDIverifier

About

C#/.NET 8 EUDI Wallet verifier (OpenID4VP) reading PID identity data (name, given name, date of birth) — bilingual web UI, QR flow, Docker.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages