Skip to content
125 changes: 118 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,124 @@ <h3>
</h3>
<p>
To be written.
</p><!--
// MARK: Present the credential request
-->
<h3>
Present the credential request
</h3>
<p>
To <dfn data-dfn-for="credential request coordinator">present the
credential request</dfn> given a [=Document=] |document|, a [=sequence=]
of validated credential requests |validatedRequests|, and an optional
{{AbortSignal}} |signal|:
</p>
<ol class="algorithm">
<li>Let |requestData| be [=struct=] consisting of |validatedRequests|,
Copy link
Collaborator Author

@marcoscaceres marcoscaceres Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with @mohamedamir and @simoneonofri... I'm going to split this out into two variables, so it's more clear.

In WebKit, this looks like:

struct DigitalCredentialsRequestData {
    Vector<ValidatedMobileDocumentRequest> requests;
    SecurityOriginData topOrigin;
    SecurityOriginData documentOrigin;
};

|document|'s [=relevant settings object=]'s [=environment settings
object/origin=], and |document|'s [=Document/origin=].
</li>
<li>Present a [=credential chooser=] with |requestData| and wait for the
Copy link
Contributor

@mohamedamir mohamedamir Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[=credential chooser=]

The credential choose linked here is the CredMan chooser which is different from the chooser intended by this algorithm I assume.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, yeah... this is where the "digital wallet" concept would have helped.... I guess even then, it would still be "show a UI to select a digital wallet with request data" and the digital wallet itself is a [=credential chooser=].

Could you help me with the wording here, @mohamedamir ... or we can come up with something together when we next chat?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one proposal:
What do you think that we define in our spec a digital credentials picker
And define as the UI displayed by the underlying platform together with digital wallets to let the user select a digital credentials in response to a digital credential presentation request.

And we can then refer to that one in our spec.
WDYT?

Happy to discuss/brainstorm in our weekly as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'm fine with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to clarify what happens when there's multiple matching requests or equivalent requests that are valid to present.

user to either:
<ul>
<li>Select a [=digital credential=] and [=holder=] that can fulfill
the request, or
</li>
Copy link
Collaborator Author

@marcoscaceres marcoscaceres Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with @mohamedamir, we are going to slot in an issue here for "Presenting with CTAP #456" so it's clear when CTAP comes into play. And we can craft text around the requirements for cross device / cross platform support here of CTAP and potentially the protocols.

note that this is independent of "Mandate one presentation protocol MUST be supported" #454.

<li>Cancel the operation.
</li>
</ul>
</li>
Comment on lines +799 to +808

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text below this makes it clear that we might exit the credential chooser due to the abort controller or an error. Those cases should be mentioned here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, @jschanck... I'll describe them.

<li>If |signal| was passed and |signal| is [=AbortSignal/aborted=]:
<ol>
<li>Return.
<p class="note" title="Abort already handled">
The algorithm [=AbortSignal/add|added=] to |signal| by the
[=credential request coordinator/prepare credential requests=]
steps handle tearing down the [=credential chooser=].
</p>
</li>
</ol>
</li>
<li>If the user cancels the operation or no credential was selected:
<ol>
<li>Let |error| be a newly created {{"AbortError"}} {{DOMException}}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why AbortError when no credential was selected or when the user cancels the operation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In both cases, "the operation was aborted [by the user]." I don't think we should distinguish them. User cancelling the operation is generally an AbortError.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree :-)
in webauthn get-algorithm-step 16

If the user exercises a user agent user-interface option to cancel the process, 
For each authenticator in issuedRequests invoke the authenticatorCancel operation on authenticator 
and remove authenticator from issuedRequests. 
Return a DOMException whose name is "NotAllowedError".

and even more explicit in the level 3

AbortError The ceremony was cancelled by an AbortController. 
See § 5.6 Abort Operations with AbortSignal and § 1.3.4 Aborting Authentication Operations.
.
.
.
NotAllowedError A catch-all error covering a wide range of possible reasons, 
including common ones like the user canceling out of the ceremony. 
Some of these causes are documented throughout this spec, while others are client-specific.

And Firefox even fixed a bug related to that for webauthn.

CredMan however, returns null when cancelling the credential chooser IIUC.

But I do think we shouldn't mix both, user cancelling and developer cancelling the request

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec is not Web Auth and we can do better. Using a catchall is not a good thing. It means developers will rely on error messages. People criticize Cred Man and Web Auth AFAIK for not using a range of exceptions.

We can elevate this to the TAG if need be, but seems like a not great use of time.

I’m strongly of the opinion that this should be an AbortError. We have a great range of exception types for a reason - we should use them.

</li>
<li>[=credential request coordinator/Complete credential request
with=] |error|.
</li>
<li>Return.
</li>
</ol>
</li>
<li>If the platform returns a platform-specific error:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the first mention of the platform, right?
So far it sounded like the browser is doing all of it.
Am I overlooking something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. However, presenting the UI and so on was presumedly done by the platform too... should clarify that.

For what it's worth, in WebKit, we have the following errors coming back potentially from the OS (from the "Identity Document Framework", which is provided by the OS/platform):

    switch (error.code) {
    case WKIdentityDocumentPresentmentErrorNotEntitled:
        exceptionData = { ExceptionCode::NotAllowedError, "Not allowed because not entitled."_s };
        break;
    case WKIdentityDocumentPresentmentErrorInvalidRequest:
        exceptionData = { ExceptionCode::TypeError, "Invalid request."_s };
        break;
    case WKIdentityDocumentPresentmentErrorRequestInProgress:
        exceptionData = { ExceptionCode::InvalidStateError, "Request already in progress."_s };
        break;
    case WKIdentityDocumentPresentmentErrorCancelled:
        exceptionData = { ExceptionCode::AbortError, "Request was cancelled."_s };
        break;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the AbortError being discussed in another comment thread, I think it's always the platform that does the credential selection.
Even in WebKit, IIUC, it is the platform that does it.
Maybe we should make it clearer in the text of the algorithm in earlier steps?
I don't know, it might be surprising to some when first hear about the platform here and not sure about its role.

WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. Open to suggestions or can try to clarify things.

Copy link
Collaborator Author

@marcoscaceres marcoscaceres Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed with @mohamedamir ... will switch WKIdentityDocumentPresentmentErrorNotEntitled to NotAllowedError.

For default case where the error is unknown, we should default to "OperationError".

I'll rewrite these in a general form and @mohamedamir will check if they align with Android's platform errors.

<ol>
<li>Let |error| be the appropriate {{DOMException}} for that error.
</li>
<li>[=credential request coordinator/Complete credential request
with=] |error|.
</li>
<li>Return.
</li>
</ol>
</li>
<li>If a [=digital credential=] was selected by the user:
<ol>
<li>Let |responseData| be a [=string=] [=digital
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this guaranteed to be a string?
should we document this somewhere that this is the expected format?

Copy link
Collaborator Author

@marcoscaceres marcoscaceres Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it has to be a string, as we have to:

  1. Check if JSON parsable/serializable (otherwise .toJSON() would break).
  2. Create the JS Object in the right JS realm (which obviously no native app can do for us).
  3. Check it is a JS Object (because the WebIDL requires it for .data).

credential/presentation response=] or [=string=] [=digital
credential/issuance response=] returned by the [=holder=].
</li>
<li>Let |parsedDataOrError| be the result of [=parse a JSON string to
a JavaScript value=] passing |responseData|.
</li>
<li>If |parsedDataOrError| is an [=exception=]:
<ol>
<li>[=credential request coordinator/Complete credential request
with=] |parsedDataOrError|.
</li>
<li>Return.
</li>
</ol>
</li>
<li>If |parsedDataOrError| is not an [=object=]:
<ol>
<li>Let |error| be a newly created {{TypeError}}.
</li>
<li>[=credential request coordinator/Complete credential request
with=] |error|.
</li>
<li>Return.
</li>
</ol>
</li>
<li>Let |protocol:DigitalCredentialProtocol| be the
{{DigitalCredentialProtocol}} [=enumeration value=] that matches the
[=digital credential/protocol identifier=] used in the exchange.
</li>
<li>Let |credential| be a newly created {{DigitalCredential}}
instance with its {{DigitalCredential/data}} initialized to
|parsedDataOrError| and its {{DigitalCredential/protocol}}
initialized to |protocol|.
</li>
<li>[=Queue a global task=] on the [=DOM manipulation task source=]
given |document|'s [=relevant global object=] to perform the
following steps:
<ol>
<li>Set the [=credential request coordinator=] [=credential
request coordinator/interaction state=] to "[=credential request
coordinator/idle=]".
</li>
<li>[=Resolve=] the [=credential request coordinator=]'s
[=credential request coordinator/active promise=] with
|credential|.
</li>
<li>Set the [=credential request coordinator=]'s [=credential
request coordinator/active promise=] to `null`.
</li>
</ol>
</li>
</ol>
</li>
</ol>
<h3>
Complete credential request with error
</h3>
Expand Down Expand Up @@ -828,13 +945,7 @@ <h3>
</li>
</ol>
</li>
</ol>
<h3>
Present the credential request
</h3>
<p>
To be written.
</p><!--
</ol><!--
// MARK: The Digital Credentials API
-->
<h2>
Expand Down