feat(openid4vci): migrate to v1.0 spec#4057
Conversation
Replace draft-era error codes (unsupported_credential_type, unsupported_credential_format) with the complete set of 7 Credential Endpoint error codes from OpenID4VCI v1.0 Section 8.3.1.2.
Core structural changes for OpenID4VCI v1.0 alignment:
- Metadata uses credential_configurations_supported map keyed by
credential_configuration_id (replaces credentials_supported array)
- Credential offers reference configuration IDs instead of inline
credential definitions (credential_configuration_ids field)
- Typed grant structs replace untyped maps in offers
- Credential requests use credential_configuration_id
- Issuer matches credentials to configurations via findCredentialConfigID
- Config IDs generated as {CredentialType}_{format}
- InvalidNonce used for nonce errors (was InvalidProof in draft)
- server_error returns HTTP 500 (was incorrectly 400)
Wallet-side changes for v1.0 alignment: - Holder resolves credential_configuration_id from issuer metadata instead of using inline credential definitions from offers - Credential requests use credential_configuration_id (v1.0 preferred) - Typed grant structs replace untyped map access - ServerError used for upstream failures (not InvalidRequest) - API handler returns non-pointer Credential in response
- Update OpenAPI schemas for v1.0 field names and structures - Update error code documentation for Credential Endpoint - Remove PreAuthorizedGrantAnonymousAccessSupported from VP authorization server metadata (belongs in VCI issuer metadata only per Section 12.3)
Three spec compliance fixes found during detailed v1.0 review:
- Credential response: use `credentials` array of wrapper objects
with `credential` key per Section 8.3
- Credential request: use `proofs` (plural) with
`{"jwt": ["..."]}` structure per Section 8.2.1
- Error response: remove c_nonce/c_nonce_expires_in fields (wallet
should use Nonce Endpoint), make c_nonce optional in holder
Update auth/ module OpenID4VCI client code for v1.0 compliance: - Use Nonce Endpoint instead of c_nonce from token response - Add credential_configuration_id to credential request and session - Change CredentialResponseEntry.Credential to json.RawMessage - Add invalid_nonce retry logic in callback - Add RequestNonce to IAM client interface - Remove c_nonce_expires_in from NonceResponse - Reject non-string @context/type entries in holder metadata parsing - Regenerate mocks and OpenAPI generated code
- Move nil body check before first field access in RequestOpenid4VCICredentialIssuance - Add comma-ok assertion on nonce claim type in issuer validateProof - Validate format field presence in holder resolveCredentialConfiguration - Add iss claim to holder proof JWT for consistency with auth module - Add tests: nil OwnDID, empty credentials, non-string nonce, missing format
|
Coverage Impact ⬇️ Merging this pull request will decrease total coverage on Modified Files with Diff Coverage (9)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
…epcopyMap Restore original comments for unchanged OAuth2 error codes. Replace JSON round-trip deepcopy with direct map copy matching the master approach.
Revert deepcopyMap to JSON round-trip: shallow copy is insufficient for nested maps like credential_definition. Remove resolved TODO about credential validation (already done via ValidateDefinitionWithCredential).
- Restore defensive panics in deepcopyMap for unmarshalable data - Make CredentialOffer.Grants a pointer with omitempty (OPTIONAL per Section 4.1.1) - Return false for non-string types in matchesCredential instead of silently skipping - Add iss claim validation in issuer proof verification per v1.0 Appendix F.1, with dedicated test case - Validate non-empty c_nonce from Nonce Endpoint responses - Remove redundant WithContext in RequestNonce
Remove draft-era Format and CredentialDefinition fields from CredentialRequest (v1.0 uses credential_configuration_id only). Rename CredentialConfigurationId(s) to CredentialConfigurationID(s) per Go naming convention for acronyms. JSON wire format unchanged.
There was a problem hiding this comment.
Pull request overview
Migrates the Nuts Node OpenID4VCI implementation from draft-11 to the OpenID4VCI v1.0 (ID1) spec across issuer/holder flows, API surface, and related client integrations.
Changes:
- Introduces the v1.0 Nonce Endpoint and moves nonce handling out of the token response (including retry-on-
invalid_noncelogic). - Updates core OpenID4VCI types to v1.0 shapes (
credential_configuration_id,proofs,credentials[], metadata map format, typed grants). - Refreshes issuer/holder/auth client logic, tests, and OpenAPI docs/codegen to match the new v1.0 contract.
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vcr/test/openid4vci_integration_test.go | Aligns integration test requests/headers and VC context/type usage. |
| vcr/openid4vci/wallet_client_test.go | Updates offer payload assertions to v1.0 fields and typed grants. |
| vcr/openid4vci/validators_test.go | Adjusts credentialSubject validation test setup. |
| vcr/openid4vci/validators.go | Refines CredentialDefinition.Validate semantics and spec link. |
| vcr/openid4vci/types_test.go | Adds v1.0 conformance tests for requests/offers/metadata/responses. |
| vcr/openid4vci/types.go | Updates OpenID4VCI types for v1.0 (nonce endpoint, config IDs, proofs, credentials array). |
| vcr/openid4vci/test.go | Updates test harness to return v1.0 credential responses and serve nonce endpoint. |
| vcr/openid4vci/issuer_client_test.go | Updates issuer client tests and adds nonce request tests. |
| vcr/openid4vci/issuer_client_mock.go | Extends mock issuer API client with RequestNonce. |
| vcr/openid4vci/issuer_client.go | Adds nonce request support and structured error parsing for credential requests. |
| vcr/openid4vci/error.go | Aligns error codes with v1.0 (e.g., invalid_nonce, invalid_credential_request). |
| vcr/issuer/test/valid/ExampleCredential.json | Updates test credential definition to v1.0 fields and example context. |
| vcr/issuer/openid_test.go | Updates issuer handler tests for config map metadata and nonce endpoint behavior. |
| vcr/issuer/openid_store_test.go | Adds standalone nonce store/consume test coverage. |
| vcr/issuer/openid_store.go | Implements standalone nonce storage/consumption in OpenID store. |
| vcr/issuer/openid_mock.go | Updates mock OpenID handler interface (token no longer returns c_nonce; adds nonce handler). |
| vcr/issuer/openid.go | Migrates issuer logic to v1.0 (config IDs, nonce endpoint, proof validation changes, config loading). |
| vcr/issuer/assets/definitions/NutsOrganizationCredential.json | Adds proof type support and updates context URL. |
| vcr/issuer/assets/definitions/NutsAuthorizationCredential.json | Adds proof type support and updates context URL. |
| vcr/holder/openid_test.go | Updates holder tests for config-id driven requests and nonce endpoint + retry flows. |
| vcr/holder/openid.go | Migrates holder flow to resolve config from metadata and use nonce endpoint + retry logic. |
| vcr/api/openid4vci/v0/issuer_test.go | Updates issuer API wrapper tests and adds nonce endpoint test. |
| vcr/api/openid4vci/v0/issuer.go | Updates credential response shape and adds nonce endpoint handler. |
| vcr/api/openid4vci/v0/holder_test.go | Updates holder API wrapper test offer payload to v1.0 structure. |
| vcr/api/openid4vci/v0/generated.go | Regenerates server interface/types/routes for nonce endpoint; removes 403 credential response type. |
| vcr/api/openid4vci/v0/api.go | Exposes nonce response type alias. |
| docs/_static/vcr/openid4vci_v0.yaml | Updates OpenAPI spec for v1.0 shapes and adds nonce endpoint definition. |
| codegen/configs/vcr_openid4vci_v0.yaml | Includes NonceResponse in generated models. |
| auth/oauth/types.go | Extends credential issuer metadata with nonce_endpoint. |
| auth/client/iam/openid4vp_test.go | Updates IAM client tests for v1.0 credentials response and nonce endpoint. |
| auth/client/iam/openid4vp.go | Adds nonce request method and updates credential retrieval signature. |
| auth/client/iam/mock.go | Updates IAM client mock signatures and adds RequestNonce. |
| auth/client/iam/interface.go | Extends IAM client interface to support nonce and config-id based credential requests. |
| auth/client/iam/client.go | Implements nonce endpoint call and v1.0 credential request/response parsing + structured errors. |
| auth/api/iam/session.go | Persists nonce endpoint + credential configuration ID for the OpenID4VCI flow. |
| auth/api/iam/openid4vci_test.go | Updates callback flow tests for nonce endpoint usage and invalid_nonce retry behavior. |
| auth/api/iam/openid4vci.go | Updates OpenID4VCI callback logic to fetch nonce, request credential by config ID, and retry on invalid_nonce. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Change Credential field from map[string]interface{} to
json.RawMessage to support any credential format (JSON-LD objects,
JWT strings). Fixes Copilot review finding and aligns vcr module
with auth module's type.
Delete duplicate CredentialRequest, CredentialResponse, and CredentialResponseEntry types from auth/client/iam. Use the canonical types from vcr/openid4vci as single source of truth. Replace local jwtTypeOpenID4VCIProof const with openid4vci.JWTTypeOpenID4VCIProof.
13 new issues
|

Summary
Migrate OpenID4VCI implementation from draft-11 to v1.0 (ID1) across both
auth/andvcr/modules.v1.0 spec alignment
c_nonceremoved from token response and error response (moved to Nonce Endpoint)credential_configuration_idsin offers replace inline credential definitionscredential_configurations_supportedin metadata changed from array to mapformat/credential_definitionremoved,credential_configuration_idis now required (credential_identifiernot supported yet)proofs(plural) replaces singularproofin credential requestcredentialsarray wrapper in credential response replaces singlecredentialinvalid_nonce,unknown_credential_configuration,invalid_credential_requestinvalid_nonceretry: Both holder and auth module fetch fresh nonce and retry onceCredentialOfferGrantsis now a typed struct instead ofmap[string]interface{}Implementation improvements
CredentialResponseEntry.Credentialchanged tojson.RawMessage(supports both JWT strings and JSON-LD objects)issclaim validation added to issuer proof verificationLimitations (unchanged from draft-11)
response). This implementation only handles one credential per request/response.
credential_offer_uri: Offers are passed as inlinecredential_offerquery parameter only, not by reference viacredential_offer_uri.credential_identifier: Onlycredential_configuration_idis accepted in credential requests. Theauthorization_detailsflow that providescredential_identifiersin the Token Response is not supported yet.