Background
How the Digital Credentials API works
A plain-language account of what the browser API actually does, and — more importantly — what it was designed to do, because the design intent explains most of the constraints we hit later.
You can skip this page if you already know the API. Everything here is background for the connector flow and the limitations that follow.
#The problem it solves
Before this API existed, a website that wanted to read a credential from your phone had two bad options.
It could bounce you out to an app using a custom link, which breaks when you have the wrong app installed, or no app, or two apps, and which gives the website no reliable way to know what happened. Or it could put a QR code on the screen and ask you to scan it with your phone, which works but is slow, is confusing on a phone where there is no second device to scan with, and has trained a generation of users to scan codes from websites, a habit the security community would rather not encourage.
The Digital Credentials API replaces both with a browser-mediated call. The site asks the browser for a credential; the browser — not the site — finds the wallets, shows the chooser, and mediates consent; the wallet returns a signed response through the browser back to the site.
#The shape of the call
A site calls the same navigator.credentials machinery used for passkeys, with a
digital member describing what it wants:
const response = await navigator.credentials.get({
digital: {
requests: [{
protocol: "openid4vp-v1-unsigned",
data: { /* an OpenID4VP authorization request */ }
}]
}
});
Three properties of that call matter more than the syntax.
The browser mediates, not the site. The site never learns which wallets are installed, never sees the chooser, and cannot drive it. This is deliberate: if a site could enumerate your wallets it would learn a great deal about you before you had agreed to anything.
The user must have acted. The call requires a genuine user gesture: a click or a tap. A page cannot ask for your licence on load.
The request names the fields. The site says exactly which data elements it
wants: given name, family name, date of birth, or just the single derived fact
age_over_18. The wallet shows the user that list and nothing broader.
#What comes back, and why it is trustworthy
The wallet returns a signed object. For a mobile driving licence in the ISO mdoc format, the chain of trust runs like this:
flowchart LR IA["Issuing authority<br/>(e.g. a road agency)"] MSO["Mobile Security Object<br/>signed by the issuer"] DEV["Device signature<br/>made at presentation time"] V["Verifier checks<br/>both signatures"] IA -->|signs at issuance| MSO MSO -->|travels with credential| V DEV -->|proves this device holds it| V style IA fill:#ffe1ce,stroke:#fc6401 style V fill:#ffe1ce,stroke:#fc6401
The issuer's signature is made once, when the licence is provisioned, and covers the data. The device signature is made fresh at each presentation and covers a transcript of the exchange, including who asked. That second signature is what stops a response being captured and replayed at a different site. It is also the source of the single most consequential constraint on the connector pattern.
#The design intent
Read the specifications and one assumption is everywhere: the site calling the API is the party that wants the data.
The request identifies the requester. The consent screen names the requester. The signed transcript binds the response to the requester's origin. The emerging registration and certification regimes around wallets are all built to answer the question "is this requester allowed to ask for this?"
The API is, in other words, designed for direct presentation. A relying party asks a wallet. There is no intermediary in the picture.
ConnectID itself is not an intermediary; it is the trust root of a federation, and the parties it federates talk to each other directly. But the connector pattern introduces one: the Connector asks the wallet on the relying party's behalf. That is not a flaw in either design — they were built for different purposes — but it does mean that putting a broker in the middle of a directly-designed protocol produces friction in predictable places, and the federation's signature is the main tool for smoothing it.
The connector flow shows where the seams are, and Limitations works through what each seam costs.
#Selective disclosure is real, and it is good
The user can prove a single fact without revealing the rest. That is better than what came before.
An age check can request age_over_18 and receive a signed boolean. Not a date of
birth from which the age is computed: a boolean, signed by the issuing authority,
with no other data attached. The verifier learns that the person is over eighteen
and learns nothing else, not even their birthday.
That is a meaningful improvement on scanning a licence, and it is available today. It also sets a standard against which the connector pattern should be judged: if the intermediary ends up requesting more than the relying party needed, the privacy gain has been spent on architecture.