Skip to content

How to build an OID4VP verifier pipeline

Field Value
Package maturity Stable (SdJwt.Net.Oid4Vp, SdJwt.Net.PresentationExchange)
Code status Mixed -- runnable package APIs with illustrative service wiring
Related concept Verifiable Credentials, HAIP
Related tutorial Tutorials
Audience Developers building relying-party (verifier) services using ASP.NET Core.
Purpose Walk through creating a Presentation Exchange request, receiving wallet responses via OID4VP, and performing three-layer verification (cryptographic, PEX constraint, business rule).
Scope Verifier DI setup, Presentation Exchange definition, authorization request generation, callback verification, and trust considerations. Out of scope: issuance (see Issuing Credentials), trust chain setup (see Establishing Trust).
Success criteria Reader can build a PEX-based credential request, verify the SD-JWT presentation response, and extract matched claims for business logic.

What your application still owns

This guide does not provide: production key custody, user authentication, authorization policy, trust onboarding, certified wallet functionality, UX and consent screens, durable audit storage, monitoring and incident response, or legal/regulatory compliance review.

Verifier pipeline overview

A typical OID4VP verifier pipeline follows these stages:

OID4VP response --> protocol validation --> nonce/audience check
  --> SD-JWT or mdoc cryptographic verification --> holder binding
  --> PEX constraint matching --> status list check
  --> trust chain resolution --> business-level decision

Some snippets are architecture-level pseudocode. For concrete APIs, see samples/SdJwt.Net.Samples.


Key decisions

Decision Options Guidance
Trust model? Static allow-list or Federation Federation for 3+ issuers
Status check failure behavior? Reject or step-up Reject for high-risk flows
Cache TTL for status/trust? Minutes to hours Shorter for critical flows
Nonce binding? Required or optional Always required for production

Prerequisites

Ensure your project references the necessary NuGet packages:

dotnet add package SdJwt.Net.Oid4Vp
dotnet add package SdJwt.Net.PresentationExchange

1. Configure the verifier service

Register verifier-related services in your dependency injection container.

using SdJwt.Net.PresentationExchange;

var builder = WebApplication.CreateBuilder(args);

// Register services used by your verifier pipeline.
// Presentation Exchange provides a concrete registration helper:
builder.Services.AddPresentationExchange();

var app = builder.Build();

2. Request data (Presentation Exchange)

When a user clicks "Login" or "Verify Age" on your site, you must formulate a request telling their wallet exactly what data you need. This guide uses the DIF Presentation Exchange v2.1.1 format.

using SdJwt.Net.Oid4Vp;
using SdJwt.Net.Oid4Vp.Verifier;

app.MapPost("/request-verification", async (/* your verifier service */ HttpContext context) =>
{
    // Define precisely what you need from the user's wallet
    var definition = new PresentationDefinition
    {
        Id = Guid.NewGuid().ToString(),
        InputDescriptors = new[]
        {
            new InputDescriptor
            {
                Id = "university_degree_requirement",
                Constraints = new Constraints
                {
                    Fields = new[]
                    {
                        // 1. Must be a University Degree
                        new Field
                        {
                            Path = new[] { "$.vc.type" },
                            Filter = new { contains = new { @const = "UniversityDegreeCredential" } }
                        },
                        // 2. We specifically require them to reveal their GPA, and it must be >= 3.0
                        new Field
                        {
                            Path = new[] { "$.vc.credentialSubject.gpa" },
                            Filter = new { type = "number", minimum = 3.0 }
                        }
                    }
                }
            }
        }
    };

    // Build and return the OID4VP Authorization Request URI
    // (your service is responsible for generating the nonce and storing state)
    var nonce = Guid.NewGuid().ToString();
    // Return definition and nonce to the frontend to render as a QR code or universal link
    return Results.Ok(new { presentation_definition = definition, nonce });
});

3. Verify the submission

Once the user approves the request in their wallet, the wallet will POST the SD-JWT string back to your CallbackUri.

app.MapPost("/api/callback", async (
    AuthorizationResponse response) =>
{
    try
    {
        // Create a VpTokenValidator with your issuer key resolver
        var vpTokenValidator = new VpTokenValidator(
            keyProvider: async jwt => await FetchIssuerKey(jwt),
            useSdJwtVcValidation: true);

        // Configure validation options
        var options = new VpTokenValidationOptions
        {
            RequireKeyBinding = true,
            ValidateIssuer = true,
            ValidIssuers = new[] { "https://trusted-issuer.example.com" },
            ValidateKeyBindingAudience = true,
            ValidKeyBindingAudiences = new[] { "https://verifier.example.com" },
            ValidateKeyBindingLifetime = true,
            ValidateKeyBindingFreshness = true,
            MaxKeyBindingAge = TimeSpan.FromMinutes(10)
        };

        var result = await vpTokenValidator.ValidateAsync(
            response,
            expectedNonce: savedNonce,
            options);

        // Result is VpTokenValidationResult
        // Access verified claims via ClaimsPrincipal
        var gpa = result.ClaimsPrincipal.FindFirst("gpa")?.Value;
        return Results.Ok($"Successfully verified candidate with GPA: {gpa}");
    }
    catch (Exception ex)
    {
        // Invalid signatures, expired tokens, etc.
        return Results.Unauthorized();
    }
});

4. Optional: HAIP Final profile validation

For high-assurance verifier deployments, validate that the verifier supports the selected HAIP Final flow and credential profile before accepting traffic.

dotnet add package SdJwt.Net.HAIP

var haipOptions = new HaipProfileOptions(); haipOptions.Flows.Add(HaipFlow.Oid4VpRedirectPresentation); haipOptions.CredentialProfiles.Add(HaipCredentialProfile.SdJwtVc); haipOptions.SupportedCredentialFormats.Add(HaipConstants.SdJwtVcFormat); haipOptions.SupportedJoseAlgorithms.Add(HaipConstants.RequiredJoseAlgorithm); haipOptions.SupportedHashAlgorithms.Add(HaipConstants.RequiredHashAlgorithm); haipOptions.SupportsDcql = true; haipOptions.SupportsSignedPresentationRequests = true; haipOptions.ValidatesVerifierAttestation = true; haipOptions.SupportsSdJwtVcCompactSerialization = true; haipOptions.UsesCnfJwkForSdJwtVcHolderBinding = true; haipOptions.RequiresKbJwtForHolderBoundSdJwtVc = true; haipOptions.SupportsStatusListClaim = true; haipOptions.SupportsSdJwtVcIssuerX5c = true;

var haipResult = new HaipProfileValidator().Validate(haipOptions); if (!haipResult.IsCompliant) { throw new InvalidOperationException("Verifier configuration does not meet the selected HAIP Final profile."); }

## Security note on trust

In the above code, `verifier.VerifyPresentationAsync` will trust any issuer that has a valid public key. In production, combine this with [OpenID Federation Trust Chains](establishing-trust.md) to ensure the issuer is authorized to issue University Degrees.

## HAIP enforcement (key decisions table)

| Decision                       | Options                                        | Guidance                                        |
| ------------------------------ | ---------------------------------------------- | ----------------------------------------------- |
| HAIP enforcement?              | None, OID4VP redirect, DC API, SD-JWT VC, mdoc | Optional hardening; match the selected flow     |