README
¶
Verifier Service
Overview
The Verifier is a unified service that enables verification of EU Digital Identity Wallet credentials through two interfaces:
- OpenID Connect (OIDC) Provider - Allows traditional OIDC Relying Parties (such as Keycloak, Auth0, or any OIDC-compliant IAM platform) to authenticate users via EU Digital Identity Wallets
- Direct OpenID4VP API - Provides direct verification capabilities for applications that want to verify credentials without going through OIDC
This service acts as a protocol translator, presenting:
- Standard OIDC interface to Relying Parties (authorization endpoint, token endpoint, userinfo endpoint)
- OpenID4VP interface to EU Digital Identity Wallets (presentation request, direct post)
- Direct verification API for custom integrations
Note: The OIDC provider functionality and credential verification are provided by a single unified verifier service (see ADR-06).
Important: Terminology Clarification
The term "issuer" has different meanings in this system:
- OIDC Issuer: The verifier acts as an OIDC Provider that issues ID tokens and access tokens to relying parties. The OIDC
issuerfield identifies the verifier itself. - VC Issuer: A separate service (see
cmd/issuer/) that issues verifiable credentials to wallets. The verifier does NOT issue credentials; it only verifies them.
The verifier verifies credentials that were previously issued by VC issuers and stored in user wallets.
Use Cases
- Enable wallet authentication in existing IAM platforms (Keycloak, Auth0, Okta, etc.)
- Add verifiable credential support to OIDC applications without code changes
- Centralized wallet verification for multiple relying parties
- Identity federation between traditional OIDC and wallet-based authentication
- Direct credential verification for custom applications
Architecture
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ │ OIDC │ │ OpenID4VP│ │
│ Keycloak │◄───────►│ Verifier │◄────────►│ EUDI Wallet │
│ (RP) │ │ (OIDC Provider │ │ (Has VCs) │
└──────────────┘ │ + VP Verifier) │ └──────────────┘
└──────────────────┘
│
▼
┌──────────┐
│ MongoDB │
└──────────┘
Features
OIDC Provider Capabilities
✅ Authorization Code Flow with PKCE
✅ Client authentication (client_secret_post, client_secret_basic, private_key_jwt)
✅ ID Token generation with verifiable credential claims
✅ UserInfo endpoint
✅ Discovery (.well-known/openid-configuration)
✅ JWKS endpoint
✅ Refresh tokens
✅ Pairwise/public subject identifiers
✅ Dynamic Client Registration (RFC 7591)
OpenID4VP Capabilities
✅ Presentation request generation
✅ Request object (signed JWT)
✅ Direct post response mode
✅ Digital Credentials API support (W3C)
✅ Credential verification (signature, status)
✅ Claim extraction and mapping
✅ QR code generation
✅ Deep link support
✅ DCQL (Digital Credentials Query Language) support
Security Features
✅ PKCE enforcement for public clients
✅ State parameter validation
✅ Nonce validation
✅ Authorization code single-use
✅ Token expiration
✅ Session timeout
✅ Pairwise pseudonymous identifiers
✅ Rate limiting on sensitive endpoints
Quick Start
Prerequisites
- Go 1.25+
- MongoDB 4.4+
- Docker (optional)
Build
# Build all services
make build
# Build verifier only
make build-verifier
# Build Docker image
make docker-build-verifier
Configuration
Create or update config.yaml:
verifier:
api_server:
addr: :8080
tls:
enabled: false
external_url: "http://localhost:8080"
# OIDC and OpenID4VP settings (nested under verifier)
oidc:
# OIDC Provider identifier - identifies this verifier service
# This is NOT related to verifiable credential issuance
# Must match the 'iss' claim in ID tokens issued to relying parties
issuer: "http://localhost:8080"
signing_key_path: "/path/to/oidc_signing_key.pem"
signing_alg: "RS256"
session_duration: 900 # 15 minutes
code_duration: 300 # 5 minutes
access_token_duration: 3600 # 1 hour
id_token_duration: 3600 # 1 hour
refresh_token_duration: 2592000 # 30 days
subject_type: "pairwise" # or "public"
subject_salt: "change-this-to-random-value"
openid4vp:
presentation_timeout: 300 # 5 minutes
supported_credentials:
- vct: "urn:eudi:pid:1"
scopes: ["openid", "profile", "email"]
- vct: "urn:eudi:ehic:1"
scopes: ["ehic"]
- vct: "urn:eudi:diploma:1"
scopes: ["diploma"]
presentation_requests_dir: "/presentation_requests"
digital_credentials:
enabled: true
use_jar: true
preferred_formats: ["vc+sd-jwt", "dc+sd-jwt", "mso_mdoc"]
response_mode: "dc_api.jwt"
allow_qr_fallback: true
common:
mongo:
uri: mongodb://localhost:27017
production: false
tracing:
addr: jaeger:4318
type: jaeger
Run
# Set config path
export VC_CONFIG_YAML=config.yaml
# Run service
./bin/vc_verifier
# Or with Docker
docker-compose up verifier
API Endpoints
OIDC Endpoints (for Relying Parties)
| Endpoint | Method | Description |
|---|---|---|
/.well-known/openid-configuration |
GET | OpenID Provider discovery metadata |
/jwks |
GET | JSON Web Key Set for token verification |
/authorize |
GET | Authorization endpoint - initiates authentication |
/token |
POST | Token endpoint - exchanges code for tokens |
/userinfo |
GET | UserInfo endpoint - returns user claims |
/register |
POST | Dynamic client registration |
/register/{client_id} |
GET/PUT/DELETE | Client configuration management |
OpenID4VP Endpoints (for Wallets)
| Endpoint | Method | Description |
|---|---|---|
/verification/request-object/{session_id} |
GET | Signed request object for wallet |
/verification/direct_post |
POST | Receives VP from wallet |
/verification/callback |
GET | Redirect with authorization code |
/qrcode/{session_id} |
GET | QR code for session |
/poll/{session_id} |
GET | Poll session status |
Session Management Endpoints
| Endpoint | Method | Description |
|---|---|---|
/session/preference |
PUT | Update session display preferences |
/verification/display/{session_id} |
GET | Credential display data |
/verification/display/{session_id}/confirm |
POST | Confirm credential display |
Integration with Keycloak
1. Add Verifier as Identity Provider
- Go to Keycloak Admin Console
- Select your realm
- Navigate to Identity Providers → Add provider → OpenID Connect v1.0
- Configure:
- Alias:
wallet - Display Name:
EU Digital Identity Wallet - Discovery Endpoint:
http://localhost:8080/.well-known/openid-configuration - Client ID:
keycloak-dev - Client Secret:
your-secret - Client Authentication:
Client secret sent as post - Validate Signatures:
ON - Use PKCE:
ON
- Alias:
2. Configure Claim Mappings
Add mappers in Keycloak to map wallet claims to user attributes:
- Username:
sub - First Name:
given_name - Last Name:
family_name - Email:
email - Birth Date:
birthdate - Nationality:
nationality
Scope to Credential Mapping
| Scope | Credential Type | Claims |
|---|---|---|
openid |
PID | sub |
profile |
PID | given_name, family_name, birthdate, nationality |
email |
PID | email |
address |
PID | address |
ehic |
EHIC | All EHIC fields |
diploma |
Diploma | All diploma fields |
Security Considerations
Subject Identifier Generation
The sub claim can be generated in two modes:
Pairwise (recommended for privacy):
sub = BASE64URL(SHA256(wallet_id + client_id + salt))
Different sub for each client prevents cross-RP correlation.
Public:
sub = BASE64URL(SHA256(wallet_id + salt))
Same sub across all clients.
Token Security
- Authorization codes are single-use and expire after 5 minutes
- PKCE is enforced for public clients and recommended for all
- Access tokens are bearer tokens, protect in transit and storage
- Refresh tokens can be rotated on each use
- All tokens should use HTTPS in production
VP Validation
The verifier validates:
- VP token signature (wallet's signature)
- Credential signature (issuer's signature)
- Credential status (not revoked)
- Presentation submission matches presentation definition
- Nonce prevents replay attacks
Development
Project Structure
cmd/verifier/ # Main entry point
main.go
internal/verifier/ # Internal implementation
apiv1/ # API handlers
client.go # API client setup
handlers.go # Health check
handler_oidc.go # OIDC endpoints (authorize, token)
handler_openid4vp.go # OpenID4VP handling
handler_api.go # Discovery, JWKS, QR code, etc.
handler_client_registration.go # Dynamic client registration
handler_session_preference.go # Session display preferences
testing.go # Test helpers
db/ # Database layer
service.go # MongoDB connection
session.go # Session CRUD
client.go # Client CRUD
httpserver/ # HTTP server
service.go # Server setup, routing
endpoints_*.go # Route handlers
static/ # Static files (HTML templates)
Testing
# Run tests
go test ./internal/verifier/...
# With coverage
go test -cover ./internal/verifier/...
Troubleshooting
Common Issues
Client not found
- Ensure client is registered in MongoDB
clientscollection - Verify
client_idmatches
Invalid redirect URI
- Check
redirect_urisarray in client document - URI must match exactly (including trailing slashes)
PKCE validation failed
- Ensure
code_verifieris sent in token request - Verify
code_challengecalculation:BASE64URL(SHA256(code_verifier))
VP validation failed
- Check credential signature and issuer trust
- Verify credential hasn't been revoked
- Ensure wallet DID is resolvable
Production Deployment
Required Changes for Production
- Enable TLS:
api_server:
tls:
enabled: true
cert_path: "/path/to/cert.pem"
key_path: "/path/to/key.pem"
- Use strong subject salt:
openssl rand -base64 32
-
Configure proper MongoDB:
- Use authentication
- Enable TLS
- Set up replication
-
Set up monitoring:
- Configure tracing endpoint
- Set up log aggregation
- Monitor session expiration
-
Secure secrets:
- Use environment variables or secret management
- Hash client secrets with bcrypt
- Rotate keys regularly
License
Same as the parent VC project.
Support
For issues and questions, see the main VC project documentation.
Documentation
¶
Overview ¶
Package docs Code generated by swaggo/swag. DO NOT EDIT
Index ¶
Constants ¶
This section is empty.
Variables ¶
var SwaggerInfo = &swag.Spec{ Version: "", Host: "", BasePath: "", Schemes: []string{}, Title: "", Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", RightDelim: "}}", }
SwaggerInfo holds exported Swagger Info so clients can modify it
Functions ¶
This section is empty.
Types ¶
This section is empty.