nuts-foundation / nuts-foundation/nuts-node
OpenID4VCI: make wallet_did optional in RequestCredential, default to the subject's did:web
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_didis provided: keep current behavior — parse it, validate ownership viasubjectOwns, fail loud withInvalidInputError("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_didis omitted: default to the subject'sdid:webDID.- Exactly one
did:webDID → use it. - Multiple
did:webDIDs → fail loud withInvalidInputError(ambiguous; caller must specifywallet_did). - Zero
did:webDIDs → fail loud withInvalidInputError(e.g. a subject that only hasdid:nuts; caller must specifywallet_did).
- Exactly one
Rationale for defaulting to did:web only:
- OpenID4VCI is in practice only used with
did:webtoday;did:nutsis not used with this flow. - A subject is not expected to have more than one
did:webDID — there's no functional reason to. - Callers that want a different DID type (e.g.
did:nuts) can still do so explicitly by passingwallet_did.
Scope
docs/_static/auth/v2.yaml— removewallet_didfromrequired, document that it defaults to the subject's soledid:webDID when omitted, and that omitting it errors when the subject has zero or multipledid:webDIDs- Regenerate the API (
make gen-api) auth/api/iam/openid4vci.go— handle the optional/omitted branches inRequestOpenid4VCICredentialIssuanceauth/api/iam/api.go— add thedid:webselection helper (filterListDIDstodid:web, enforce exactly one)- Tests, docs, release notes
Potential problems / footguns
- Implicit DID selection hides where the credential lands. With
wallet_didomitted, 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 resolvedwallet_didin the response so the choice is observable. - "Sole did:web" can change over time. A subject that has exactly one
did:webtoday may gain a second later, silently flipping previously-working omit-the-field calls intoInvalidInputError. 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 atwallet_did. - Convenience may entrench a bad assumption. Defaulting only to
did:webbakes "OpenID4VCI == did:web" into the API surface. Ifdid: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 towarddid:web.
Considerations
- Selection is intentionally restricted to
did:webrather than reusingdetermineClientDID(which pickscandidateDIDs[0]and enforces theentity_idclient_id scheme). A blind "first DID" could pick adid:nutsDID and silently change behavior — the explicitdid:web-only rule avoids that. - Failure modes are explicit: ambiguous (multiple
did:web) and empty (nodid:web) both returnInvalidInputError, 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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