SolidusIdentity
Create Your DID

What Happens When You Scan a Credential Request QR Code

Scanning does not tell you who is asking. The scanner checks that the code contains well-formed JSON of the expected shape and that one field is a string beginning with did:, and that is the entire check on the requester's identity.

The rest of this page is what the scan does do, in the order the code does it.

Step by step, from the code

  1. The camera reads a QR code. This is a real camera and a real decoder, getUserMedia and the jsQR library, running in your browser. Nothing here is simulated.
  2. The payload is parsed as JSON and must declare itself a solidus-credential-request. Anything else is rejected.
  3. Three fields are required: a requester identifier that starts with did:, a requester name, and the type of credential being requested.
  4. A pending request is written to your account, before you have agreed to anything.
  5. You are shown who is asking and what they want, and you pick a credential and the specific claims, or you deny.
  6. On approval, a share record is created with a 30-day expiry.

Step 3 is not a verification, and calling it one would be the lie

did: is a prefix. The identifier is never resolved, never fetched, and never checked against any registry of known issuers or verifiers.

A QR code that says it is from your bank is a QR code that says it is from your bank. The scan does not make it true, and nothing downstream in this flow makes it true either.

The endpoint is called validate, and what it validates is the shape of the message, not the identity of the sender. Those are different things and the name invites the confusion.

What the product gets right about this, stated before the criticism, because it earned it

The backend does not pretend. Its response carries a verification flag that is hard-coded to false, with an explicit comment saying DID document verification is deferred. It did not quietly default to true, and it did not omit the field so that nobody would ask.

And the interface honours the flag. The verification badge is rendered only when that flag is true, so, today, never. There is no false trust marker on the screen. A great many products would have shown a green shield for "we successfully parsed the QR code", and this one does not.

That is the right failure mode. The gap is that the check is missing, not that a missing check is being dressed up as a passing one, and those are very different kinds of problem to have.

The scan itself is a write

Step 4 is worth pausing on. Merely pointing your camera at a valid-looking code inserts a pending row into your account, before any consent screen. It is scoped to you, it does nothing on its own, and it is what makes the approve/deny step addressable, a reasonable implementation choice, and still a write you did not ask for. If you scan ten codes out of curiosity, you have ten pending requests.

What approving actually does, and does not

Approving updates the request's status and, when you have selected a credential and claims, inserts a share record that expires in 30 days. The guards are correct: a request that is not yours returns "not found", and a request that is no longer pending returns a conflict, so you cannot approve the same thing twice.

And then nothing is sent. No presentation is assembled, no message goes to the requester, no callback fires. The end state of this flow is a row in our database. The person who showed you the QR code receives nothing from it, whatever they get, they get by some other means you and they arranged.

We checked the backend for any outbound delivery and found none, with a control confirming the search reaches the code. So "scan to share" today means "scan to record that you agreed to share".

There is a second flow, and it is the one that actually delivers

We would have published the paragraph above as the whole story, and it would have understated our own product. A backend-only search sees backend behaviour. Checking the browser code found a different surface, /present, where a verifier's request arrives as a link rather than through the scanner, and where the disclosure is posted back to the verifier's own endpoint.

Two things about it are genuinely well built, and one is a hard limitation:

  • The callback is bound to the requester's domain. The response address must be https and its hostname must match the domain in the request exactly, checked when the request is parsed and re-checked at the moment of sending, so a disclosure cannot be redirected to another host. The comment in the code claims defense in depth; the implementation matches the comment. We checked, because a comment is not evidence.
  • The disclosure is built in your browser, from credential material this browser already holds.
  • Which is also the limitation: if this browser does not hold it, the flow cannot run. There is no server-side fallback that would let us assemble it for you.

The page also measures your device to decide how to generate the proof. We are not publishing that measurement, here or anywhere: a number from one browser is not a performance claim, and turning it into one is exactly the move this site exists to avoid.

The two flows are not connected. The scanner does not drive the delivering one. If you are building against this, that distinction is the most useful sentence on the page.

What you cannot check yourself

The scanner is behind authentication, /qr-scanner redirects to login, and a deliberately fake path returns 404, so the redirect means the page is real rather than that everything redirects. This page is code-read evidence, not the run-it-yourself kind.

Our content plan calls this page /scan-to-share. There is no such route: it returns 404. The real surfaces are /qr-scanner and /share. This is the fifth plan route in this product that does not match the application, and we would rather say so on the page than quietly redirect you.

Keep reading

What Happens When You Scan a Credential Request QR Code · Solidus — Solidus Identity