feat: W3C Digital Credentials API support via @sirosfoundation/dc-api - #504
Conversation
…-api Integrates the @sirosfoundation/dc-api library for DC API protocol constants, feature detection, and native invocation. The verifier polyfill adds transport fallbacks (redirect, QR, SSE/poll). New files: - dc-api.js: vendored bundle of @sirosfoundation/dc-api (3.2 kB) - dc-api-polyfill.js: verifier integration importing the library, adding same-device redirect + cross-device SSE/poll fallbacks Updated: - presentation-definition.html/js: tries DC API first, falls back to QR - authorize_enhanced.html: uses polyfill for credential requests Detection uses W3C-specified feature checks: - typeof DigitalCredential !== 'undefined' - DigitalCredential.userAgentAllowsProtocol(protocol) Ref: W3C Digital Credentials API spec, CS-07 Dep: https://github.com/sirosfoundation/dc-api
ac37cdc to
1488b74
Compare
- dc-api.js: rebuild bundle with es2022 target, var→const - dc-api-polyfill.js: use export...from, globalThis, RegExp.exec(), optional chaining, move SSE error handler to outer scope - presentation-definition.html: add id/label for select (a11y), add aria-label for group toggle checkbox - presentation-definition.js: remove unused imports, extract _tryNativeDCAPI() and _setupFallbackFlow() to reduce cognitive complexity, use optional chaining + globalThis
Adds newly exported: OID4VP_ALL_PROTOCOLS, OID4VP_SPEC_PROTOCOLS, isOID4VPProtocol
There was a problem hiding this comment.
Pull request overview
This PR adds W3C Digital Credentials API (DC API) support in the verifier UI with a native-first flow (via a vendored @sirosfoundation/dc-api bundle + a verifier-specific polyfill), and introduces a new apigw datastore capability to generate pre-authorized OpenID4VCI credential offers tied to datastore documents.
Changes:
- Add DC API integration + polyfill fallbacks in verifier static UI (importmaps, native invocation, QR/SSE/poll fallbacks).
- Add apigw datastore pre-authorized offer endpoint (
POST /api/v1/datastore/preauth_offer) and wire issuance to a newdatastoreauth provider. - Update Swagger artifacts + swagger generation inputs to include newly referenced packages/types.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/model/auth_providers.go | Adds AuthProviderDatastore constant. |
| Makefile | Updates apigw swagger generation dirs (adds pkg/openid4vp). |
| internal/verifier/staticembed/presentation-definition.js | Attempts native DC API first; falls back to existing QR/link/SSE flow. |
| internal/verifier/staticembed/presentation-definition.html | Adds importmap entries; minor accessibility fixes. |
| internal/verifier/staticembed/dc-api.js | Vendored ESM bundle for DC API helper library. |
| internal/verifier/staticembed/dc-api-polyfill.js | New verifier-oriented DC API wrapper + redirect/SSE/poll fallback transport. |
| internal/verifier/staticembed/authorize_enhanced.html | Switches verifier UI to use the new polyfill and vendored bundle. |
| internal/apigw/httpserver/service.go | Registers new datastore endpoint /preauth_offer. |
| internal/apigw/httpserver/endpoints_datastore.go | Adds httpserver handler for datastore pre-auth offer. |
| internal/apigw/httpserver/endpoints_datastore_test.go | Extends mock apiv1 interface to compile with new method. |
| internal/apigw/httpserver/api.go | Extends Apiv1 interface with DatastorePreAuthOffer. |
| internal/apigw/apiv1/handlers_issuer.go | Allows datastore data source to omit identifier; treats datastore as session-based provider for doc lookup. |
| internal/apigw/apiv1/handlers_issuer_assertion_test.go | Updates expectation: datastore allows empty identifier. |
| internal/apigw/apiv1/handlers_datastore.go | Implements DatastorePreAuthOffer (pre-auth code + cached docs + offer URL). |
| internal/apigw/apiv1/handlers_datastore_test.go | Adds tests for DatastorePreAuthOffer behavior and persistence. |
| docs/apigw/swagger.yaml | Documents /api/v1/datastore/preauth_offer and request/reply schemas. |
| docs/apigw/swagger.json | Same as above (JSON swagger). |
| docs/apigw/docs.go | Same as above (embedded swagger template). |
Files not reviewed (1)
- docs/apigw/docs.go: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The comment incorrectly referenced 'openid4vp-v1-unsigned' as the example protocol check. The library actually prefers signed requests via getBestProtocol() (signed > multisigned > unsigned).
|
Two comments:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- docs/apigw/docs.go: Generated file
Comments suppressed due to low confidence (2)
internal/verifier/staticembed/presentation-definition.js:498
requestCredential()is being called withpresentationDefinition.authorization_request, which is anopenid4vp://...authorization-request URI (client_id + request_uri). The DC API expects the OpenID4VP request data (e.g.,{ request_uri, client_id }) or a signed request JWT, so passing the full URI string will be treated as a JWT and the native DC API call will fail, making the “native” path effectively non-functional.
const result = await requestCredential(
this.presentationDefinition.authorization_request,
{ signal: abortController.signal },
);
internal/verifier/staticembed/presentation-definition.js:493
this._dcAbortis assigned but never used anywhere in this module, so it becomes dead state and can confuse future maintenance. If cancellation is not implemented, keep the AbortController local (or wire_dcAbort.abort()into an existing UI cancel action).
const abortController = new AbortController();
this._dcAbort = abortController;
Addresses s-jairl's two comments on the PR: - The DCQL playground UI (presentation-definition.js) always attempted the native W3C Digital Credentials API regardless of server config. Thread it through the existing verifier.digital_credentials.enable flag (already respected by authorize_enhanced.html) via a new dc_api_enabled field on /ui/metadata, instead of adding a duplicate toggle. - When the user cancelled/declined the native DC API picker, the native call rejects with NotAllowedError. dc-api-polyfill.js's requestCredential() silently swallowed that and fell through to its own cross-device wait (SSE/poll, up to timeoutMs = 5min by default), even though both callers (presentation-definition.js, authorize_enhanced.html) already have their own immediate QR/wallet- link fallback UI. This left the page stuck on a loading spinner after cancelling, on both desktop Chromium and Android Chromium/ Vanadium. Let native failures propagate directly to callers instead; the polyfill fallback path is now only reached when native DC API isn't available/usable at all, not as a retry after a completed native attempt.
|
Addressed both points (commit 123241c):
|
|
Perhaps you want to change the settings group ".verifier.digital_credentials" to something like ".verifier.dcapi" instead? While I guess it is correct, "digital credentials" is a slightly wider term. While you're at it, consider running the documentation update script. |
# Conflicts: # internal/verifier/staticembed/presentation-definition.js
|
Re-based onto current main and resolved the conflict (a small JS conflict — my `dcApiEnabled` field vs. a concurrent JSDoc type update, kept both). Ran `make gen-config-docs` per your comment (commit fce1aa5) — the diff includes some unrelated already-existing config that had drifted out of sync too (Kafka SASL/mTLS, rate limiting), not just this PR's changes. On the `.verifier.digital_credentials` → `.verifier.dcapi` rename: I didn't make this change.
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
internal/verifier/staticembed/presentation-definition.js:506
requestCredential()is being called withpresentationDefinition.authorization_request, which (perCreateAuthorizationRequestURI) is anopenid4vp://...authorization-request URI containingclient_id+request_uri. The dc-api library expects the OpenID4VP request data (e.g.{client_id, request_uri}or a signed request JWT), not the full URI string; passing the URI asrequestwill likely make the native DC API invocation fail and always fall back.
const result = await requestCredential(
this.presentationDefinition.authorization_request,
{ signal: abortController.signal },
);
internal/verifier/staticembed/presentation-definition.js:517
- The
catch (err)block assumeserralways has.nameand.message. If a non-Error is thrown (e.g. a string or arbitrary object), the logging line can itself throw and prevent the QR/links fallback from being set up.
} catch (err) {
if (err.name === 'AbortError') return true;
console.log("DC API not available or failed, falling back to QR/links:", err.message);
return false;
internal/verifier/staticembed/dc-api-polyfill.js:319
_waitViaPoll()registers anabortlistener but doesn't remove it on the non-abort exit paths (success, failure, timeout). IfrequestCredential()is called multiple times, this can accumulate listeners and cause extra rejections on later aborts.
if (Date.now() > deadline) {
reject(new DOMException('Timeout waiting for wallet response', 'AbortError'));
return;
}



Adds W3C Digital Credentials API support to the verifier frontend using the
@sirosfoundation/dc-apilibrary.When the browser supports the DC API, credential presentation happens natively. Otherwise the existing QR/redirect/SSE flow is used unchanged.
New files
dc-api.js— vendored ESM bundle of@sirosfoundation/dc-api(protocol constants, detection, native invocation, error helpers)dc-api-polyfill.js— verifier integration layer: imports from the library, adds same-device redirect + cross-device SSE/poll fallbacksModified files
presentation-definition.html— importmap entries for library + polyfill; accessibility fixes (label/id for select, aria-label for toggle)presentation-definition.js— tries DC API first via polyfill, falls back to QR/wallet links + SSE; refactored for reduced cognitive complexityauthorize_enhanced.html— importmap for the library; uses polyfill for credential requests