Implementer guide — implementing EAA issuance with OpenID4VCI¶
Audience: EAA Providers implementing issuance flows with OpenID4VCI in the German EUDI Wallet Ecosystem.
Contents¶
- Introduction
- EAA issuance architecture and infrastructure
- OpenID4VCI option space and rulebook choices
- Implementing OpenID4VCI for your EAA configuration
- Protocol templates (parameterized)
- Implementation checklists
This guide covers building the issuer and getting credentials into wallets. Post-issuance topics — validity, refresh, revocation, key rotation, incident response, and deferred issuance — live on the separate Credential Lifecycle & Operations page.
1. Introduction¶
This guide targets EAA Providers who want to issue EAAs into German EUDI Wallets using the OpenID for Verifiable Credential Issuance protocol (OID4VCI). This guide is not intended to re-document the OID4VCI protocol, but attempts to provide additional conceptual explanation and EUDI-specific considerations. For implementers, it will always be advisable to study the specification alongside this document. This document also complements the rulebook guidance: while the rulebook (the human-readable technical and governance specification) defines what your credential is and which level of end-to-end assurance you target, this document explains how to turn those decisions into a concrete OpenID4VCI-based issuance flow.
Understanding Key Terminology
- Catalog of attestations: A registry where you can look up what attestations are issued in the ecosystem
- Schema metadata: A machine-consumable entry in the catalog that references the rulebook, schema, and trust list for authorized issuers
- Rulebook: The human-readable specification covering technical requirements and governance for a credential type
- Trust list: A list of authorized issuers and their certificates for a given credential type
Sandbox: Do I need to start with a rulebook?
No, if you are a prospective EAA Provider who wants to start testing a solution in the Sandbox, you don't need to have a rulebook defined out of the gate. You can start with the implementation of your issuance infrastructure and write/publish your rulebook, trust list and schema metadata when you are ready.
Known Limitations¶
Before starting implementation, be aware of these current ecosystem limitations:
Authorization Code Flow with PID for identification: Using the Authorization Code Flow with the PID for identification depends on extensions to the Authorization Code Flow that still require standardization and is only expected to become available in Q2 2026. There are alternative, though less elegant, ways of combining a PID Presentation with an OpenID4VCI issuance flow. Which solution fits best for you likely depends on your context. See Section 3.3.1 for details.
Document Structure¶
Chapter 2 gives a high-level overview of the components and infrastructure needed to run an issuance service. Chapter 3 explains the OpenID4VCI option space (grant types, proof mechanisms, formats) and how these relate to choices made in your rulebook, in particular around identification. Chapter 4 provides a practical implementation path once you have selected your EAA configuration (format, assurance target, and flow). Once your issuer is live, the Credential Lifecycle & Operations page covers the post-issuance topics: validity, refresh, revocation, key rotation, incident response, and deferred issuance.
Where relevant, we call out aspects that are particularly important for the German EUDI Sandbox. This is a living document and guidance may change as technical details are further standardized and clarified.
1.1 Quick start — how do I get my credential into the wallet?¶
Annoyingly, the answer is: it depends — mostly on the end-to-end assurance target set by your rulebook. Choosing the grant type and flow is how you implement phase 3 of the EAA Provider roadmap, once your rulebook (phase 2) is settled. In this guide, we can't cover all possible configurations of OpenID4VCI, so to give implementers something concrete to work with, we lay out two profiles that should apply for many use cases. Although your use case may not fit neatly in one of these two categories, that does not mean that you can't achieve your use case with the appropriate assurance level and user experience. In this guide, we cover the following profiles:
- High fidelity (LoA High or Substantial) — the credential has legal, regulatory, or safety implications, or the rulebook requires robust identification. Expect strong online identification (PID or equivalent), strict attribute verification at issuance time, and strong holder-binding proofs. → Use the Authorization Code flow (HAIP-like). For a complete, step-by-step walkthrough, see the HAIP example flow.
- Low fidelity (LoA Low) — event tickets, simple membership attestations, or low-risk attributes. Identification may be prior or optional, attribute verification can be lightweight, and attacker impact is limited. → Pre-authorized codes or QR/deep-link offers are usually sufficient; focus on short-lived, one-time codes. For a complete, step-by-step walkthrough, see the low-fidelity example flow.
Whichever you choose, always reflect the rulebook's stated assurance target in your implementation decisions (identification, token lifetime, refresh policies, and auditing). HAIP is presented as an example high-assurance profile; it is not mandatory to implement.
2. EAA issuance architecture and infrastructure¶
This chapter describes the main technical building blocks required for EAA issuance and how they relate to your rulebook.
2.1 Core components¶
At a minimum, an EAA Provider needs the following components:
Issuance backend
Application logic that determines which users are eligible for which credentials and when. It connects to source systems, implements business rules from your rulebook, and orchestrates issuance flows.
OpenID4VCI credential issuer
An HTTP API exposing the OpenID4VCI endpoints:
- Issuer metadata (
/.well-known/openid-credential-issuer) - Credential offer generation (for example, a QR code or deep link)
- Token endpoint (if the authorization server is co-located — see §2.2)
- Credential endpoint
This component implements the OpenID4VCI protocol and translates between wallet-facing messages and your internal data.
Trust and signing subsystem
Infrastructure to manage signing keys and certificates for EAAs, connection to the trust framework (your EAA Provider registration and trust anchors), integration with the relevant trust lists in the German ecosystem, and key rotation, backup, and revocation. This subsystem must meet the end-to-end assurance target described in your rulebook.
Authentic source integration
The systems that hold the authoritative data for your credential attributes — registries, core databases, or line-of-business systems. Your issuance backend must retrieve and validate data from these systems at issuance time, according to the attribute verification rules in your rulebook.
Logging, monitoring, and audit trail
Logging of issuance events, errors, and security-relevant actions, with retention and access control aligned to your governance requirements.
2.2 Optional components¶
Depending on your use case and rulebook decisions, you may additionally run:
-
User self-service portal
Allows holders to request credentials, view status, or revoke/renew credentials (where supported). -
Back-office operations console
For manual review, exception handling, and audit. -
Separate authorization server
OpenID4VCI distinguishes between the authorization server (which authenticates the user and issues access tokens) and the credential issuer (which constructs and signs credentials). These roles can be deployed together or separately. A separate AS is useful when:- Your organization already operates an OAuth 2.0/OIDC server for other services and wants to reuse it for credential issuance
- You want to separate authentication from credential construction for security or organizational reasons
- Multiple credential issuers share a single authorization server
When the AS is separate, your issuer metadata's
authorization_serversfield points to it. The wallet fetches the AS metadata independently and uses its token endpoint. The AS must issue tokens scoped and audience-restricted to your credential issuer.
2.3 Security Considerations¶
For each of these components, you should follow best practice for Oauth 2.0 Security. All endpoints involved in the issuance process should be TLS protected. The details are stipulated in the FAPI2 Security Profile.
2.4 Relation to the rulebook¶
Your rulebook and your issuance infrastructure are tightly coupled:
The rulebook defines:
- Credential semantics (attributes, formats, identifiers)
- End-to-end assurance target
- Identification and attribute verification methods
- Revocation and update strategies
The infrastructure must enforce:
- Identification flows compatible with the stated assurance target
- Attribute retrieval and checks from the source systems named in the rulebook
- Secure operation of the signing and status mechanisms
The OpenID4VCI profile you choose (Chapter 3) is one of the key implementation levers to achieve the rulebook’s assurance target.
3. OpenID4VCI option space and rulebook choices¶
This chapter explains the main OpenID4VCI choices an EAA Provider must make and how they relate to the rulebook.
3.1 OpenID4VCI in brief¶
OpenID4VCI specifies how wallets obtain verifiable credentials from an issuer. At a high level, the flow is:
- The EAA Provider creates a credential offer and delivers it to the wallet (for example via QR code or deep link).
- The wallet and issuer perform an authorization and token exchange (using either authorization code or pre-authorized code).
- The wallet calls the credential endpoint with proof-of-possession and receives the credential.
Your rulebook does not describe protocol details but does define the assurance and governance requirements that your OpenID4VCI deployment must satisfy. OID4VCI flows vary along two key dimensions: who initiates the issuance, and on which device the offer is received. §3.2 covers these; the grant type and other choices follow from them.
3.2 Issuance flow variations¶
Two orthogonal dimensions — who initiates the flow, and on which device — shape how you generate and deliver the credential offer. Both are described in OID4VCI Section 3.3.3.
Issuer-initiated vs wallet-initiated¶
Issuer-initiated (most common): The issuer generates a credential offer — a URI containing the credential_issuer URL and the list of offered credential configurations — and delivers it to the user, typically as a QR code or a deep link. The user's wallet scans the QR code or follows the link to start the flow. This is the pattern described throughout the example flows in this guide.
Wallet-initiated: The wallet starts the flow without a credential offer from the issuer. The wallet may already know what credentials a given issuer provides (for example, from prior configuration or ecosystem discovery), and the user selects a credential to request directly from the wallet UI. In the Authorization Code flow, the wallet sends an authorization request without an issuer_state parameter — the issuer has not pre-generated a credential offer.
The German EUDI Wallet currently supports the issuer-initiated pattern. If you are designing a wallet-initiated flow, check current wallet documentation for supported variations.
Same-device vs cross-device¶
Same-device: The user is on the same device as their wallet (typically their phone). If you present a credential offer as a deep link (openid-credential-offer://...), the OS opens the wallet app directly. This gives a seamless, one-tap experience.
Cross-device: The user is on a different device from their wallet — for example, on a desktop browser completing a form or an enrollment step. The credential offer is displayed as a QR code that the user scans with their wallet on their phone. The wallet then independently contacts your issuer, while the desktop session waits for the issuance to complete (typically via polling or a server-sent event).
Both patterns use the same credential offer structure. The difference is purely in how the offer is presented to the user and which device processes it. Your credential offer endpoint should support both by providing a URI that can be rendered as a QR code for cross-device use and as a deep link for same-device use.
See OID4VCI Appendix H.1 (same-device) and Appendix H.2 (cross-device) for worked examples.
3.3 Grant types: authorization code vs pre-authorized code¶
OID4VCI supports two main issuance patterns (see Section 3.4 and Section 3.5):
-
Authorization code
The holder is redirected to the issuer’s authorization server, performs an interactive authentication and consent step, and the wallet exchanges the authorization code for an access token. -
Pre-authorized code
The issuer creates a credential offer containing a pre-authorized code that is already bound to a specific user or account. The wallet uses this code directly at the token endpoint (optionally with a one-time user code or PIN) to obtain an access token, without an interactive redirect.
3.3.1 Identification and grant type¶
Identification is defined by your rulebook and by the surrounding trust framework, not by the grant type itself. However, there are typical pairings:
Authorization code with strong online identification:
- Suitable when you identify the user at issuance time via PID or another high-assurance process defined by your rulebook.
- The authorization server handles user authentication; the result is linked to issuance.
- The rulebook should state that identification happens at issuance and specify the method (for example PID-based online identification or a sector-specific process).
Pre-authorized code with prior identification
- Suitable when identification is not needed, or has already taken place in a different channel (for example, in-person onboarding at a branch or a fully compliant remote onboarding process).
- The issuance flow itself relies on that prior identification and on secure code delivery to the same person.
- The rulebook should state that identification is performed before issuance, how it is verified, and how the pre-authorized code is securely delivered and bound to that identified person.
In both cases, your end-to-end assurance target from the rulebook must consider:
- Strength of user identification (PID, sector process, account history)
- Security of the issuance infrastructure (including authorization server, credential issuer, and source systems)
- Security of the pre-authorized code lifecycle (generation, delivery, replay prevention)
The choice between authorization code and pre-authorized code does not change the assurance target, but it changes where and how you must provide evidence that the target is met.
Retrieving additional information during identification
Implementers should keep in mind that the presentation of a PID does not automatically give you a unique identifier to match against your own records. Consequently, the identification process may require you to retrieve additional data to allow you to make a unique match against internal source information.
3.4 Credential formats and rulebook decisions¶
From the rulebook you choose:
- Supported credential formats (dc+sd-jwt, mso_mdoc, or both)
- Required attributes and their semantics
- Requirements on selective disclosure and data minimization
From an OpenID4VCI perspective, this leads to:
- Definition of credential configuration identifiers (vct values or document types)
- Supported proof types and algorithms for holder binding and signing
- Mapping between internal attribute names and the technical schemas (JSON Schema, CDDL) defined for this credential type
These choices should be consistent with the EUDI and German ecosystem profiles you are targeting.
Example: credential offers reflecting format decisions
If your rulebook specifies SD-JWT VC, the credential offer references the corresponding configuration identifier:
{
"credential_issuer": "https://city.example/issuer",
"credential_configuration_ids": ["city_pass_sd_jwt"],
"grants": {
"authorization_code": {
"issuer_state": "abc123"
}
}
}
If your rulebook supports both formats, the offer can list both — the wallet picks the one it supports:
{
"credential_issuer": "https://city.example/issuer",
"credential_configuration_ids": ["city_pass_sd_jwt", "city_pass_mdoc"],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "xyz789"
}
}
}
The values in credential_configuration_ids must match exactly the keys you declare under credential_configurations_supported in your issuer metadata. See §4.2 and §5.1 for the full metadata structure.
3.5 Other relevant options¶
Proof-of-possession (PoP) mechanisms¶
The wallet proves it controls the private key that will be bound to the issued credential by sending a proof-of-possession with its credential request. The OID4VCI spec defines three proof types:
jwt (Appendix F.1)
The wallet creates a signed JWT containing the issuer as audience and a c_nonce to prevent replay. This is the most widely supported proof type and the one used in the German ecosystem today.
attestation (Appendix F.3)
A key attestation-based proof, where the device's secure hardware (e.g., TEE or secure enclave) attests that the key is held in protected hardware. This provides stronger assurance about key security than a plain JWT proof. Support for this proof type is expected in a future version of the ecosystem.
di_vp (Appendix F.2)
Data Integrity Verifiable Presentation-based proofs are NOT used in the EUDI Ecosystem. Relevant for credential ecosystems using W3C Data Integrity instead of JOSE.
For the German EUDI ecosystem, use jwt. Declare this in your credential configuration under proof_types_supported:
Credential response encryption¶
Credential responses are already protected by HTTPS (TLS) — the same encryption that secures all web traffic. The OID4VCI spec also defines an optional additional layer: the wallet can ask the issuer to encrypt the credential response with the wallet's own public key, on top of TLS. This means the credential can only be decrypted by the wallet, even if someone were somehow able to read the TLS traffic. Standard HTTPS is sufficient for the majority of deployments. Adding response encryption requires both the issuer and wallet to implement extra cryptographic logic, and the security benefit is narrow.
The cases where you might consider it:
- Your infrastructure routes traffic through a TLS-terminating proxy or load balancer that you do not fully control, and you need to ensure credential contents are protected beyond that point
- A specific data protection or security policy explicitly requires application-layer encryption of credential data
Otherwise, focus on getting TLS right (strong cipher suites, valid certificates) and skip this option.
Other options¶
- Multiple credentials in one flow — The
batch_credential_issuancecapability lets you issue multiple credential instances in a single flow. Declarebatch_credential_issuance.batch_sizein your issuer metadata if you support this (relevant for anti-tracking via multiple unlinkable instances). - Error handling and retry policies — For high-assurance flows, define how the wallet should behave on errors at the token and credential endpoints, including appropriate rate limiting and backoff.
4. Implementing OpenID4VCI for your EAA configuration¶
This chapter outlines a practical path to implement OpenID4VCI issuance once your rulebook and EAA profile are defined. It is the reference for why each step exists; for an end-to-end worked walkthrough with concrete payloads, read it alongside the HAIP and low-fidelity example flows.
4.1 Step 1 — Derive your issuance configuration from the rulebook¶
From your rulebook and the associated schema metadata entry in the catalog of attestations, extract the following decisions into a concrete OpenID4VCI configuration:
- Credential types and formats you will issue (credential identifiers, technical schema references, versions)
- End-to-end assurance target and associated controls
- Identification method (at issuance time or prior) and how it maps to:
- Authorization code flows, or
- Pre-authorized code flows
- Requirements for attribute verification (which source systems must be queried at issuance time)
- Revocation and update behavior (whether the credential supports updates, and how revocation status is exposed)
Document this configuration as a separate technical specification for your development team and integrators.
4.2 Step 2 — Publish issuer metadata¶
Issuer metadata is the first thing a wallet reads. Before any credential changes hands, the wallet fetches your metadata to discover what you issue, in which formats, which authorization server to use, where your endpoints are, and which proof types and algorithms you accept. If a required field is missing or inconsistent with what you actually implement, the wallet either cannot start the flow or completes it and then rejects the credential. Treat the metadata document as the public contract for your issuer: it must reflect your rulebook decisions about formats, assurance, and supported flows, and it must match what your endpoints actually do.
How metadata discovery works¶
When a wallet receives a credential offer containing your credential_issuer URL, it fetches your issuer metadata from a well-known URI derived from that URL. It then uses your metadata to:
- Confirm you support the credential configuration(s) listed in the offer
- Discover your authorization server (from
authorization_servers), fetch its metadata, and determine the token endpoint - Determine which proof types and algorithms you require
- Fetch a nonce (if you expose a nonce endpoint) before calling the credential endpoint
The metadata therefore drives the entire flow. Incorrect or incomplete metadata is one of the most common integration issues.
Where the wallet looks¶
Per OID4VCI Section 12.2, issuer metadata is served from a well-known URI derived from your credential_issuer identifier. The well-known path segment is inserted between the host and the issuer's path (following RFC 8414) — it is not appended to the end. For an issuer identifier of https://issuer.example/issuers/playground, the metadata URL is:
Appending /.well-known/openid-credential-issuer to the end of a path-bearing issuer identifier is a common source of 404s.
Issuer-level fields¶
These describe the issuer as a whole and where its endpoints live. Each should be derived from a rulebook or infrastructure decision:
| Field | Purpose | Driven by |
|---|---|---|
credential_issuer |
Your issuer identifier (a URL). | Infrastructure |
authorization_servers |
Array of the authorization server(s) that issue access tokens. May be co-located with the issuer or a separate/chained AS. | Flow & identification design (§3.3) |
credential_endpoint |
Where the wallet exchanges a token + proof for a credential. | Infrastructure |
nonce_endpoint |
Where the wallet fetches a fresh proof nonce (see Proof nonces below). | Infrastructure |
deferred_credential_endpoint |
Present only if you support deferred issuance. Note the ecosystem wallet-support caveat in Lifecycle §6. | Rulebook (issuance timing) |
notification_endpoint |
Where the wallet reports issuance outcomes (credential stored, deleted, etc.) per OID4VCI §10.3. Not yet supported by the EUDI Wallet reference implementation — omit until support is confirmed. | Infrastructure |
status_list_aggregation_endpoint |
Where status information for revocation is published/aggregated. | Rulebook revocation policy (§vi) |
batch_credential_issuance |
Declares support (and batch_size) for issuing multiple instances in one flow. |
Rulebook (anti-tracking / multiple instances) |
display |
Issuer name and logo, per locale — the issuer identity the wallet shows the user. | Branding |
Per-credential configuration¶
credential_configurations_supported maps a configuration id to the technical definition of one issuable credential. That id is the value wallets place in a credential offer's credential_configuration_ids. For each entry:
| Field | Purpose | Driven by |
|---|---|---|
format |
dc+sd-jwt or mso_mdoc. |
Rulebook format decision (§3.4) |
vct (SD-JWT) / doctype (mdoc) |
The credential type identifier. | Rulebook semantic identity |
scope |
Optional OAuth scope a wallet can request to obtain this configuration. | Flow design |
cryptographic_binding_methods_supported |
How the credential is bound to the holder's key (e.g. jwk). |
Rulebook holder-binding policy |
credential_signing_alg_values_supported |
Algorithms you sign credentials with (e.g. ES256). |
Rulebook signature requirements (§v) |
proof_types_supported |
The proof mechanism(s) and algorithms the wallet must use for proof-of-possession (today jwt; an attestation proof type is expected in future). |
Ecosystem profile |
credential_metadata.display |
Per-credential card rendering (name, logo, colors, background image). | See styling guidance below |
Proof nonces¶
Proof-of-possession JWTs sent by the wallet to the credential endpoint must include a c_nonce to prevent replay. OID4VCI §7 defines two ways to provide this nonce:
Token or credential response (current EUDI Wallet behavior): Include c_nonce (and optionally c_nonce_expires_in) directly in the token response or credential response. The EUDI reference wallet expects the nonce here.
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"c_nonce": "1a105ffb-0c70-4a83-b86b-8c803de3009d",
"c_nonce_expires_in": 86400
}
Nonce endpoint (optional): Advertise a nonce_endpoint in your issuer metadata. The wallet sends an empty POST and receives a fresh nonce before each credential request. Implement this if you need to support wallets that use it, but it is not required for the EUDI reference wallet.
For compatibility with the EUDI reference wallet, returning c_nonce in the token response is sufficient.
Authorization server metadata¶
If your AS is separate from the issuer (or chained), the wallet discovers it from authorization_servers and then fetches the AS's own metadata from its .well-known/oauth-authorization-server URI (again using the inserted-path form). A high-assurance authorization server in this ecosystem typically advertises the Authorization Code flow with PAR (pushed_authorization_request_endpoint), PKCE (code_challenge_methods_supported: ["S256"]), and DPoP (dpop_signing_alg_values_supported). These align with the high-fidelity path in §3.2.
Display and rendering¶
Note the two distinct display layers: issuer-level display (your organization's name and logo) and per-configuration credential_metadata.display (how the credential card itself renders). For guidance on attribute ordering, labels, and logos, see Credential Styling Guidance rather than duplicating it here.
A worked example¶
For concrete, current metadata you can inspect a live issuer. EUDIPLO is an open-source issuer implementation operated as a public playground; the credential offer for its university-diploma configuration points at the issuer https://eudiplo.eudi-wallet.org/issuers/playground, whose metadata you can fetch at:
Illustrative, not normative
EUDIPLO is one example implementation
A trimmed view of one credential configuration from that document:
"university-diploma": {
"format": "dc+sd-jwt",
"scope": "diploma",
"vct": "urn:eudi:eaa:university-diploma:1",
"cryptographic_binding_methods_supported": ["jwk"],
"credential_signing_alg_values_supported": ["ES256"],
"proof_types_supported": {
"jwt": { "proof_signing_alg_values_supported": ["ES256"] }
},
"credential_metadata": {
"display": [
{
"name": "University Diploma",
"locale": "en-US",
"description": "Academic Degree Certificate",
"background_color": "#1e3a5f",
"text_color": "#FFFFFF"
}
]
}
}
See §5.1 for a parameterized template you can adapt.
4.3 Step 3 — Implement authorization and token handling¶
For authorization code flows:
- Integrate with your authorization server (or embedded authorization component) that supports:
- Strong user authentication according to your rulebook
- User consent for issuance
- Binding of the authenticated user to the resulting authorization code
- Implement the token endpoint logic:
- Validate the authorization code
- Issue an access token with claims that identify the user and the intended credential configuration
- Enforce audience and scope restrictions suitable for issuance
For pre-authorized code flows:
- Implement secure generation and storage of pre-authorized codes bound to identified users or accounts.
- Decide how codes are delivered to users (out-of-band channel, secure portal, etc.), in a way that maintains the rulebook’s assurance requirements.
- Implement the token endpoint logic:
- Validate pre-authorized codes and optional user codes
- Enforce one-time use and expiry
- Issue access tokens that carry enough information to drive issuance while respecting data minimization.
In both cases, treat the token endpoint as part of your high-assurance infrastructure, with proper rate limiting, logging, and monitoring.
4.4 Step 4 — Implement the credential endpoint¶
The credential endpoint is where the wallet exchanges proof-of-possession and an access token for an actual credential. A typical implementation performs the following steps:
- Validate the access token:
- Signature and issuer
- Audience and intended use (issuance)
- Expiry and freshness
- Determine the credential configuration:
- From the request or from the access token
- Check that the requested configuration is allowed for this token and user
- Validate the proof-of-possession from the wallet:
- Check signature validity
- Check that the proof is bound to the credential issuer and to the current credential request
- Fetch and verify attributes from source systems:
- Retrieve required data
- Apply the attribute verification rules from the rulebook
- Construct the credential:
- Populate all required attributes
- Apply the correct technical schema (JSON Schema or CDDL) and credential identifiers (vct or doctype)
- Enforce data minimization and selective disclosure requirements
- Sign and return the credential:
- Use the signing keys from your trust and signing subsystem
- Apply algorithms and formats that match your credential configuration
- Return the credential in the format expected by the wallet.
5. Protocol templates (parameterized)¶
The templates in this chapter are structurally realistic but parameterized and non-normative. Replace placeholders with the values required by the German ecosystem profile you are targeting.
5.1 Credential issuer metadata (template)¶
{
"credential_issuer": "https://issuer.example/issuers/eaa",
"authorization_servers": ["https://issuer.example/issuers/eaa"],
"credential_endpoint": "https://issuer.example/issuers/eaa/vci/credential",
"nonce_endpoint": "https://issuer.example/issuers/eaa/vci/nonce",
"notification_endpoint": "https://issuer.example/issuers/eaa/vci/notification",
"display": [
{ "name": "Example EAA Provider", "locale": "en-US",
"logo": { "uri": "https://issuer.example/assets/logo.png" } }
],
"credential_configurations_supported": {
"eaa_example_sd_jwt": {
"format": "dc+sd-jwt",
"vct": "urn:example:eaa:1",
"scope": "eaa_example",
"cryptographic_binding_methods_supported": ["jwk"],
"credential_signing_alg_values_supported": ["ES256"],
"proof_types_supported": {
"jwt": { "proof_signing_alg_values_supported": ["ES256"] }
},
"credential_metadata": {
"display": [
{ "name": "Example Credential", "locale": "en-US",
"background_color": "#1e3a5f", "text_color": "#FFFFFF" }
]
}
},
"eaa_example_mdoc": {
"format": "mso_mdoc",
"doctype": "org.example.eaa.1",
"cryptographic_binding_methods_supported": ["cose_key"],
"credential_signing_alg_values_supported": ["ES256"],
"proof_types_supported": {
"jwt": { "proof_signing_alg_values_supported": ["ES256"] }
}
}
}
}
authorization_servers (see §4.2). If your issuer requires c_nonce in proof-of-possession proofs — which is recommended — return it in the token response; the EUDI reference wallet expects it there. A nonce_endpoint is an optional alternative for wallets that support it. See §4.2 for both approaches.
The nonce endpoint is optional. The EUDI Wallet still expects the dpop nonce to be included in the access token.
5.2 Credential offers (templates)¶
Authorization code grant:
{
"credential_issuer": "https://issuer.example",
"credential_configuration_ids": ["eaa_example_sd_jwt"],
"grants": {
"authorization_code": {
"issuer_state": "<opaque_state>"
}
}
}
Pre-authorized code grant:
{
"credential_issuer": "https://issuer.example",
"credential_configuration_ids": ["eaa_example_sd_jwt"],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "<one_time_code>",
"tx_code": {
"input_mode": "numeric",
"length": 6,
"description": "Enter the PIN sent to your registered phone number."
}
}
}
}
Security note: - Treat the pre-authorized code as a bearer secret. Anyone who obtains it may be able to redeem it. - Prefer delivering pre-authorized codes via an authenticated or out-of-band channel (for example, a logged-in portal, in-person handover, or a second channel you already trust for the identified holder). - Use the pre-authorized code in combination with a PIN if needed. - Use compensating controls: short expiry, one-time use, rate limiting, and require a user PIN where appropriate.
5.3 Token requests (templates)¶
Authorization code token request:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=<authorization_code>&
redirect_uri=<redirect_uri>&
client_id=<wallet_client_id>
Pre-authorized code token request:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&
pre-authorized_code=<one_time_code>&
tx_code=<transaction_code>
5.4 Credential request (template)¶
POST /credential HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
{
"credential_configuration_id": "eaa_example_sd_jwt",
"proof": {
"proof_type": "jwt",
"jwt": "<proof_jwt>"
}
}
In the German ecosystem, proof-of-possession at the OpenID4VCI layer uses a JWT proof (proof_type: "jwt"). This proof mechanism is independent of the credential format you issue (dc+sd-jwt or mso_mdoc) and is used to bind issuance to a wallet-held key. In the future, the 'attestation' proof_type will also be supported.
6. Implementation checklists¶
6.1 Decision checklist (rulebook-to-protocol mapping)¶
Before building, ensure your rulebook answers:
- When is the holder identified: during issuance (online) or prior to issuance (re-used)?
- Which grant type do you support: authorization code, pre-authorized code, or both?
- Which credential formats do you issue: dc+sd-jwt, mso_mdoc, or both?
- Which source systems must be queried at issuance time?
- What is your end-to-end assurance target and which operational controls are mandatory?
6.2 Metadata checklist¶
- Metadata is reachable over HTTPS and returns correct content type.
- Metadata declares the same capabilities you actually implement:
- supported formats and configuration identifiers
- supported grant types
- token and credential endpoints
- supported algorithms and proof types required by the ecosystem profile
6.3 Token endpoint checklist¶
- Validate grant type and required parameters.
- Enforce expiry and one-time use semantics for codes.
- Ensure tokens intended for issuance are not accepted at other APIs and vice versa (audience scoping).
- Bind tokens to:
- intended credential configuration(s)
- intended subject / eligibility record
- intended audience (issuance)
- Rate limit and log suspicious activity.
- Log token grants using pseudonymous identifiers where possible to support audits without violating data minimization.
6.4 Credential endpoint checklist¶
- Validate access token (signature, expiry, audience, scope/claims).
- Validate wallet proof-of-possession according to the ecosystem profile.
- Perform source system lookups and apply the rulebook's attribute verification rules.
- Construct credential using the correct technical schemas and credential identifiers (vct for SD-JWT, doctype for mDoc), and enforce data minimization.
- Ensure status / revocation information required by the rulebook is correctly attached or referenced.
- Sign using protected keys and return the correct format (dc+sd-jwt or mso_mdoc).
- Ensure error responses do not leak sensitive information but are specific enough for wallet implementers to debug.