|
|
| Status |
Proposed |
| Author |
SD-JWT .NET Team |
| Created |
2026-03-04 |
| Packages |
SdJwt.Net.Oid4Vci (extension), SdJwt.Net.Wallet (extension) |
| Specifications |
OpenID4VCI 1.0 Section 11.2, OID4VCI Credential Issuer Display |
Context / Problem Statement
Wallets display credentials to users, but currently there is no standard way for issuers to communicate how their credentials should appear. This leads to:
- Inconsistent branding: Same credential type looks different across wallets
- Missing context: Users cannot distinguish credential variants (e.g., gold vs silver membership)
- No localization: Display text is hardcoded in the wallet, not provided by the issuer
- Poor user trust: Credentials without recognizable branding are less trustworthy to end users
Two complementary capabilities are needed:
- Issuer Metadata: Credential-type-level branding (logo, color, description, background image) published by the issuer in their OID4VCI metadata
- Embedded Display Data: Per-instance display hints embedded in the credential itself (e.g., event ticket with seat number, tier badge, expiry countdown)
Goals
- Parse and store OID4VCI credential issuer display metadata
- Render credential cards in wallets with issuer-provided branding
- Support per-credential-instance display hints (embedded in the VC)
- Support localization (multi-language display metadata)
- Support both SD-JWT VC and mdoc credential formats
Non-Goals
- Define wallet UI rendering engine (wallet-specific)
- Require issuers to provide display metadata (optional enhancement)
- Support animated or interactive display elements
Proposed Design
Architecture
flowchart LR
subgraph Issuer["Issuer"]
IssuerMeta["Issuer Metadata Endpoint"]
CredBuilder["Credential Builder"]
end
subgraph Wallet["Wallet"]
MetaFetcher["Metadata Fetcher<br/>(new)"]
DisplayRenderer["Display Renderer<br/>(new)"]
CredStore["Credential Store"]
end
IssuerMeta -->|"GET /.well-known/openid-credential-issuer"| MetaFetcher
MetaFetcher --> CredStore
CredBuilder -->|"display claim in VC"| CredStore
CredStore --> DisplayRenderer
Component Design
public class CredentialDisplayMetadata
{
public string Name { get; set; }
public string Description { get; set; }
public string Locale { get; set; }
public string LogoUri { get; set; }
public string LogoAltText { get; set; }
public string BackgroundColor { get; set; } // Hex color
public string TextColor { get; set; } // Hex color
public string BackgroundImageUri { get; set; }
}
CredentialInstanceDisplay (Embedded in VC)
public class CredentialInstanceDisplay
{
public string Title { get; set; } // e.g., "Gold Membership"
public string Subtitle { get; set; } // e.g., "Valid through 2026"
public string BadgeUri { get; set; } // Tier badge image
public Dictionary<string, string> Labels { get; set; } // Claim display labels
}
public class IssuerMetadataService
{
public Task<CredentialIssuerMetadata> FetchAsync(string issuerUrl);
public Task<CredentialDisplayMetadata> GetDisplayAsync(
string issuerUrl, string credentialType, string locale = "en");
}
Sequence: Credential Display Resolution
sequenceDiagram
participant Issuer
participant Wallet
participant User
Note over Wallet: Credential received via OID4VCI
Wallet->>Issuer: GET /.well-known/openid-credential-issuer
Issuer-->>Wallet: Metadata with display array
Wallet->>Wallet: Cache display metadata
Wallet->>Wallet: Merge type-level + instance-level display
Wallet->>User: Render credential card with branding
API Surface
// Issuer side: Add display hints to credential
var credential = new VerifiableCredentialBuilder()
.WithType("GoldMembership")
.WithClaim("member_name", "Alice Johnson")
.WithClaim("tier", "Gold")
.WithDisplayHint(new CredentialInstanceDisplay
{
Title = "Gold Membership",
Subtitle = "Since 2024",
Labels = new() { ["member_name"] = "Member Name", ["tier"] = "Tier" }
})
.Build();
// Wallet side: Fetch and merge display metadata
var metadataService = new IssuerMetadataService(httpClient);
var display = await metadataService.GetDisplayAsync(
issuerUrl: "https://issuer.example.com",
credentialType: "GoldMembership",
locale: "en");
// display.Name = "Example Corp Membership"
// display.LogoUri = "https://issuer.example.com/logo.png"
// display.BackgroundColor = "#1a1a2e"
// display.TextColor = "#f5f5f5"
Security Considerations
| Concern |
Mitigation |
| Malicious logo/image URI |
Wallet validates URI scheme (HTTPS only), caches images, optionally uses CSP |
| Display metadata spoofing |
Metadata fetched from issuer's well-known endpoint, validated with issuer key |
| Excessive metadata size |
Size limits on logos (configurable), image dimensions capped |
| Tracking via logo requests |
Wallet caches logos and uses opaque fetch (no cookies/referrer) |
Estimated Effort
| Component |
Effort |
CredentialDisplayMetadata models |
1 day |
CredentialInstanceDisplay models |
1 day |
IssuerMetadataService |
2 days |
DisplayRenderer abstraction |
2 days |
| OID4VCI metadata parsing extension |
2 days |
| Tests + documentation |
2 days |
| Total |
10 days |