Skip to content

Add credential_selection to access token request API #4090

@stevenvegt

Description

@stevenvegt

Parent PRD

#4067 (see PRD comment)

What to build

Add credential_query as an optional field on the ServiceAccessTokenRequest and wire it through the full access token request flow.

Call stack

The credential_query flows through the following layers:

API handler: RequestServiceAccessToken (auth/api/iam/api.go)
  extracts credential_query from request body
  → RequestRFC021AccessToken (auth/client/iam/openid4vp.go)
    accepts credentialQueries as new parameter
    → wallet.BuildSubmission (vcr/holder/interface.go)
      accepts credentialQueries as new parameter alongside presentationDefinition
      → presenter.buildSubmission (vcr/holder/presenter.go)
        if credentialQueries provided:
          selector, _ = pe.NewDCQLSelector(credentialQueries, pd, pe.FirstMatchSelector)
          builder.SetCredentialSelector(selector)
        → builder.Build(format)

Changes per layer

OpenAPI spec (docs/_static/auth/v2.yaml): add credential_query (optional array of DCQL credential query objects) to ServiceAccessTokenRequest.

Generated types (auth/api/iam/generated.go): regenerate to include new field.

API handler (auth/api/iam/api.go): extract credential_query from request.Body, pass to RequestRFC021AccessToken.

Client interface (auth/client/iam/interface.go): add credentialQueries []dcql.CredentialQuery parameter to RequestRFC021AccessToken.

RFC021 client (auth/client/iam/openid4vp.go): accept credentialQueries, pass to wallet.BuildSubmission.

Wallet interface (vcr/holder/interface.go): add credentialQueries []dcql.CredentialQuery parameter to BuildSubmission, placed alongside presentationDefinition since they work together — the PD defines what's required, the queries narrow which specific credentials to use.

BuildSubmission(ctx context.Context, walletDIDs []did.DID,
    additionalCredentials map[did.DID][]vc.VerifiableCredential,
    presentationDefinition pe.PresentationDefinition,
    credentialQueries []dcql.CredentialQuery,
    params BuildParams,
) (*vc.VerifiablePresentation, *pe.PresentationSubmission, error)

SQL wallet (vcr/holder/sql_wallet.go): pass credentialQueries through to presenter.

Presenter (vcr/holder/presenter.go): if credentialQueries is non-empty, create a DCQL selector via pe.NewDCQLSelector(credentialQueries, pd, pe.FirstMatchSelector) and call builder.SetCredentialSelector(selector).

Design decisions

  • credential_query is a function parameter on BuildSubmission (not in BuildParams) because it's about which credentials to select, not about how to build the VP. BuildParams contains VP construction parameters (audience, format, nonce, expiry).
  • The DCQL selector is constructed in the presenter layer, keeping PE/DCQL concerns out of the API and client layers.
  • Coexistence: existing credentials array (self-attested VCs) continues to work alongside credential_query.
  • No match_policy parameter — the DCQL filter must narrow to exactly one credential per input descriptor. See decision comment.

Example request

{
  "scope": "medicatieoverdracht-gf",
  "authorization_server": "https://verifier.example.com",
  "credential_query": [
    {
      "id": "id_patient_enrollment",
      "claims": [
        {
          "path": ["credentialSubject", "hasEnrollment", "patient", "identifier", "value"],
          "values": ["123456789"]
        }
      ]
    }
  ]
}

Acceptance criteria

  • credential_query field added to ServiceAccessTokenRequest in OpenAPI spec (optional array)
  • Generated types include new field
  • RequestServiceAccessToken handler extracts and passes credential_query through the flow
  • RequestRFC021AccessToken accepts credentialQueries parameter
  • Wallet.BuildSubmission accepts credentialQueries parameter alongside presentationDefinition
  • Presenter creates DCQL selector when credentialQueries is non-empty
  • Existing credentials (self-attested) array works alongside credential_query
  • Request without credential_query behaves identically to current behavior
  • Mocks and test call sites updated for new interface signatures
  • End-to-end test: API call with credential_query selects correct credential from wallet
  • End-to-end test: API call with credential_query matching zero credentials returns descriptive error
  • End-to-end test: API call with credential_query matching multiple credentials returns descriptive error
  • Caching: different credential_query values produce different cache keys (existing hash-based cache handles this)
  • Descriptive error messages for credential selection failures (user story 13)

Blocked by

User stories addressed

  • User story 1: specify which patient's credential via API
  • User story 2: specify which organization type credential via API
  • User story 3: only provide credential_query for ambiguous types
  • User story 4: clear error on zero matches via API
  • User story 5: clear error on ambiguous matches via API
  • User story 6: standard DCQL syntax in API
  • User story 7: self-attested credentials alongside credential_query
  • User story 8: credential_query ID maps to PD input descriptor ID
  • User story 12: descriptive error messages

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions