nuts-foundation / nuts-foundation/nuts-node

OpenID4VCI: make wallet_did optional in RequestCredential, default to the subject's did:web

Open
#4,365 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
28
Forks
23
Avg merge
1d 10h
Merged PRs (30d)
76

Description

Context

Follow-up to the v2 API design. RequestOpenid4VCICredentialIssuance (POST /internal/auth/v2/{subjectID}/request-credential) currently requires the caller to pass wallet_did in the request body — the DID that should receive the credential.

In practice, v2 API consumers (EHRs) work with the subject of a care organization, not with its DIDs. Requiring wallet_did forces the caller to first make a separate call to enumerate the subject's DIDs just to pick one to pass back in. That extra round-trip is friction for limited functional gain.

Problem

Endpoint Takes wallet/holder DID? How DID is chosen
request-service-access-token No determineClientDID → first DID from ListDIDs (api.go:1013)
request-user-access-token No derived from subject internally
request-credential (this issue) Yes, required caller-supplied, validated against ListDIDs

Proposal

Make wallet_did optional in the request body.

  • When wallet_did is provided: keep current behavior — parse it, validate ownership via subjectOwns, fail loud with InvalidInputError("wallet DID does not belong to the subject") if it is not owned by the subject. No change for existing callers (fully backwards compatible).
  • When wallet_did is omitted: default to the subject's did:web DID.
    • Exactly one did:web DID → use it.
    • Multiple did:web DIDs → fail loud with InvalidInputError (ambiguous; caller must specify wallet_did).
    • Zero did:web DIDs → fail loud with InvalidInputError (e.g. a subject that only has did:nuts; caller must specify wallet_did).

Rationale for defaulting to did:web only:

  • OpenID4VCI is in practice only used with did:web today; did:nuts is not used with this flow.
  • A subject is not expected to have more than one did:web DID — there's no functional reason to.
  • Callers that want a different DID type (e.g. did:nuts) can still do so explicitly by passing wallet_did.

Scope

  • docs/_static/auth/v2.yaml — remove wallet_did from required, document that it defaults to the subject's sole did:web DID when omitted, and that omitting it errors when the subject has zero or multiple did:web DIDs
  • Regenerate the API (make gen-api)
  • auth/api/iam/openid4vci.go — handle the optional/omitted branches in RequestOpenid4VCICredentialIssuance
  • auth/api/iam/api.go — add the did:web selection helper (filter ListDIDs to did:web, enforce exactly one)
  • Tests, docs, release notes

Potential problems / footguns

  • Implicit DID selection hides where the credential lands. With wallet_did omitted, the caller no longer states which DID holds the issued credential. If the response doesn't echo the chosen DID, the caller has no record of where it went. Mitigation: return the resolved wallet_did in the response so the choice is observable.
  • "Sole did:web" can change over time. A subject that has exactly one did:web today may gain a second later, silently flipping previously-working omit-the-field calls into InvalidInputError. The behavior is correct (fail loud), but it's a latent break for callers that relied on the default — worth a clear error message pointing at wallet_did.
  • Convenience may entrench a bad assumption. Defaulting only to did:web bakes "OpenID4VCI == did:web" into the API surface. If did:nuts (or another method) is ever used with this flow, the default becomes misleading rather than just incomplete. The explicit-DID escape hatch keeps it correct, but reviewers should be comfortable with the API nudging callers toward did:web.

Considerations

  • Selection is intentionally restricted to did:web rather than reusing determineClientDID (which picks candidateDIDs[0] and enforces the entity_id client_id scheme). A blind "first DID" could pick a did:nuts DID and silently change behavior — the explicit did:web-only rule avoids that.
  • Failure modes are explicit: ambiguous (multiple did:web) and empty (no did:web) both return InvalidInputError, never a silent fallback or panic.

Related

  • Triggering rationale: v2 API consumers (EHRs) operate on subjects, not DIDs.
  • Precedent: request-service-access-token, request-user-access-token (subject-only).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with auth/api/iam/openid4vci.go and auth/api/iam/api.go, then review docs/_static/auth/v2.yaml and the existing subject DID validation. Run make gen-api and inspect the related tests before adding coverage for an explicit wallet_did, exactly one did:web, zero did:web, and multiple did:web cases. Done means the API docs, generated API, tests, and release notes reflect the specified behavior, including the resolved wallet DID in the response if required.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, authentication
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.