Skip to content

feat(openid4vci): migrate to v1.0 spec#4057

Open
JorisHeadease wants to merge 19 commits intomasterfrom
feature/openid4vci-v1
Open

feat(openid4vci): migrate to v1.0 spec#4057
JorisHeadease wants to merge 19 commits intomasterfrom
feature/openid4vci-v1

Conversation

@JorisHeadease
Copy link
Contributor

@JorisHeadease JorisHeadease commented Mar 9, 2026

Summary

Migrate OpenID4VCI implementation from draft-11 to v1.0 (ID1) across both auth/ and vcr/ modules.

v1.0 spec alignment

  • Nonce Endpoint (Section 7): New dedicated POST endpoint for nonce retrieval
  • c_nonce removed from token response and error response (moved to Nonce Endpoint)
  • credential_configuration_ids in offers replace inline credential definitions
  • credential_configurations_supported in metadata changed from array to map
  • Credential Request (Section 8.2): format/credential_definition removed, credential_configuration_id is now required (credential_identifier not supported yet)
  • proofs (plural) replaces singular proof in credential request
  • credentials array wrapper in credential response replaces single credential
  • Error codes aligned with Section 8.3.1.2: invalid_nonce, unknown_credential_configuration, invalid_credential_request
  • invalid_nonce retry: Both holder and auth module fetch fresh nonce and retry once
  • CredentialOfferGrants is now a typed struct instead of map[string]interface{}

Implementation improvements

  • CredentialResponseEntry.Credential changed to json.RawMessage (supports both JWT strings and JSON-LD objects)
  • iss claim validation added to issuer proof verification
  • Input validation hardening: nil body check, nonce type assertion

Limitations (unchanged from draft-11)

  • Single credential only: v1.0 uses a single endpoint for both single and batch issuance (multiple proofs in request → multiple credentials in
    response). This implementation only handles one credential per request/response.
  • No credential_offer_uri: Offers are passed as inline credential_offer query parameter only, not by reference via credential_offer_uri.
  • No credential_identifier: Only credential_configuration_id is accepted in credential requests. The authorization_details flow that provides
    credential_identifiers in the Token Response is not supported yet.

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
@qltysh
Copy link

qltysh bot commented Mar 9, 2026

Qlty

Coverage Impact

⬇️ Merging this pull request will decrease total coverage on master by 0.04%.

Modified Files with Diff Coverage (9)

RatingFile% DiffUncovered Line #s
Coverage rating: B Coverage rating: B
vcr/issuer/openid.go81.6%149, 168, 245-249...
Coverage rating: A Coverage rating: B
vcr/holder/openid.go85.3%133-138, 215-216...
Coverage rating: A Coverage rating: A
vcr/openid4vci/validators.go100.0%
Coverage rating: B Coverage rating: B
auth/client/iam/openid4vp.go100.0%
Coverage rating: C Coverage rating: B
vcr/api/openid4vci/v0/issuer.go87.5%115-116
Coverage rating: B Coverage rating: C
vcr/openid4vci/issuer_client.go61.4%103, 122-123...
Coverage rating: A Coverage rating: A
vcr/issuer/openid_store.go100.0%
Coverage rating: B Coverage rating: A
auth/api/iam/openid4vci.go89.1%45-47, 85-86
Coverage rating: B Coverage rating: C
auth/client/iam/client.go75.6%249-250, 253-254...
Total81.8%
🤖 Increase coverage with AI coding...

In the `feature/openid4vci-v1` branch, add test coverage for this new code:

- `auth/api/iam/openid4vci.go` -- Lines 45-47 and 85-86
- `auth/client/iam/client.go` -- Lines 249-250, 253-254, 258-259, 267-268, 270-271, and 374
- `vcr/api/openid4vci/v0/issuer.go` -- Line 115-116
- `vcr/holder/openid.go` -- Lines 133-138, 215-216, 219-220, 230-231, 234-235, 281-282, and 302
- `vcr/issuer/openid.go` -- Lines 149, 168, 245-249, 278-279, 333-337, 386-396, 423, 467-468, 491-492, 524, 528, 575, 586-587, 591-592, 596-597, 599-600, 604-605, 620-621, and 625-626
- `vcr/openid4vci/issuer_client.go` -- Lines 103, 122-123, 126-127, 131-132, 134-139, 143-144, and 159-160

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

…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.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_nonce logic).
  • 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.
@JorisHeadease JorisHeadease marked this pull request as ready for review March 11, 2026 10:25
@JorisHeadease JorisHeadease linked an issue Mar 11, 2026 that may be closed by this pull request
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.
@qltysh
Copy link

qltysh bot commented Mar 11, 2026

13 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 12): RequestOpenid4VCICredentialIssuance 12
ripgrep Lint // TODO: This check is too simplistic, there can be multiple credential_configuration_ids, 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update OpenID4VCI implementation to spec v1.0

2 participants