Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API to show error messages from failed token fetches #498

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
143 changes: 114 additions & 29 deletions spec/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,50 @@ dictionary DisconnectedAccount {
</xmp>

<!-- ============================================================ -->
### The CredentialRequestOptions ### {#browser-api-credential-request-options}
## The IdentityCredentialError Interface ## {#browser-api-identity-credential-error-interface}
<!-- ============================================================ -->

This specification introduces a new type of {{DOMException}}, the {{IdentityCredentialError}}. It
is used when the [=user agent=] does not receive an identity assertion after the user has requested
to use a federated account.

<xmp class="idl">
dictionary IdentityCredentialErrorInit {
DOMString error;
DOMString url;
};
</xmp>

<pre class="idl">
[Exposed=Window, SecureContext]
interface IdentityCredentialError : DOMException {
constructor(optional DOMString message = "", optional IdentityCredentialErrorInit options = {});
npm1 marked this conversation as resolved.
Show resolved Hide resolved
readonly attribute DOMString error;
readonly attribute DOMString url;
};
</pre>

<div algorithm="IdentityCredentialError constructor">
The {{IdentityCredentialError/constructor()}}, given a |realm|, a {{DOMString}} |message| and a
{{IdentityCredentialErrorInit}} |options| must run the following steps:
npm1 marked this conversation as resolved.
Show resolved Hide resolved
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. Invoke the {{DOMException}}'s {{DOMException/constructor()}}, passing |realm| and |message|.
1. Set <a>this</a>.{{IdentityCredentialError/error}} to |options|.{{IdentityCredentialError/error}}
if it is present in |options|, or to "" otherwise.
1. Set <a>this</a>.{{IdentityCredentialError/url}} to |options|.{{IdentityCredentialError/url}}
if it is present in |options|, or to "" otherwise.
</div>

<dl>
: <b>{{IdentityCredentialError/error}}</b>
:: The {{IdentityCredentialError/error}}'s attribute getter returns the value it is set to.
It represents the type of error which resulted in an {{IdentityCredential}} not being created.
: <b>{{IdentityCredentialError/url}}</b>
:: The {{IdentityCredentialError/url}}'s attribute getter returns the value it is set to.
npm1 marked this conversation as resolved.
Show resolved Hide resolved
It represents a URL where the user can learn more information about the error.
</dl>

<!-- ============================================================ -->
## The CredentialRequestOptions ## {#browser-api-credential-request-options}
<!-- ============================================================ -->

This section defines the dictionaries passed into the JavaScript call:
Expand Down Expand Up @@ -772,7 +815,7 @@ dictionary IdentityProviderRequestOptions : IdentityProviderConfig {
</dl>

<!-- ============================================================ -->
### The <code>\[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)</code> internal method ### {#browser-api-rp-sign-in}
## The <code>\[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)</code> internal method ## {#browser-api-rp-sign-in}
<!-- ============================================================ -->

The {{Credential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)}}
Expand Down Expand Up @@ -810,7 +853,7 @@ requests.
When the {{IdentityCredential}}'s
<dfn for="IdentityCredential" method>\[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)</dfn>
algorithm is invoked, the user agent MUST execute the following steps. This returns an
{{IdentityCredential}} (or throws an error to the caller).
{{IdentityCredential}} or throws an error to the caller.

1. Assert: These steps are running [=in parallel=].
1. If the [=list/size=] of
Expand All @@ -833,10 +876,10 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu
1. Let |credential| be the result of running [=create an IdentityCredential=] with |provider|,
|options|, and |globalObject|.
1. If |credential| is a pair:
1. Let |throwImmediately| be the value of the second element of the pair.
1. Let |delayException| be the value of the second element of the pair.
1. The user agent SHOULD wait a random amount of time
before the next step if all of the following conditions hold:
* |throwImmediately| is false
* |delayException| is true
* The <dfn>promise rejection delay</dfn> was not disabled by
[=setdelayenabled|user agent automation=]
* The user agent has not implemented another way to prevent
Expand All @@ -851,22 +894,24 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu
However, UAs may have different UI approaches here and prevent it in a different way.
1. [=Queue a global task=] on the [=DOM manipulation task source=]
to throw a new "{{NetworkError}}" {{DOMException}}.
1. Otherwise, return |credential|.
1. Otherwise, return |credential| (this may throw since it may be an
npm1 marked this conversation as resolved.
Show resolved Hide resolved
npm1 marked this conversation as resolved.
Show resolved Hide resolved
{{IdentityCredentialError}}).
</div>

<!-- ============================================================ -->
### Create an IdentityCredential ### {#create-identity-credential}
<!-- ============================================================ -->

The <a>create an IdentityCredential</a> algorithm invokes the various FedCM fetches, shows the user
agent UI, and creates the {{IdentityCredential}} that is then returned to the [=RP=].
agent UI, and creates the {{IdentityCredential}} or {{IdentityCredentialError}} which is then
returned to the [=RP=].

<div algorithm="create an IdentityCredential">
To <dfn>create an IdentityCredential</dfn> given an {{IdentityProviderRequestOptions}}
|provider|, a {{CredentialRequestOptions}} |options|, and a
|globalObject|, run the following steps. This returns an {{IdentityCredential}}
or a pair (failure, bool), where the bool indicates whether to skip delaying
the exception thrown.
|globalObject|, run the following steps. This returns an {{IdentityCredential}}, an
{{IdentityCredentialError}}, or a pair (failure, bool), where the bool indicates whether to delay
throwing the exception.
1. Assert: These steps are running [=in parallel=].
1. Let |loginStatus| be the result of [=get the login status=] with
the [=/origin=] of |provider|'s {{IdentityProviderConfig/configURL}}.
Expand Down Expand Up @@ -975,7 +1020,7 @@ the exception thrown.
the user agent MAY show some UI to the user indicating that they are being
<dfn>auto-reauthenticated</dfn>.
1. Set |isAutoSelected| to true.
1. Otherwise, if |mediation| is "{{CredentialMediationRequirement/silent}}", return (failure, true).
1. Otherwise, if |mediation| is "{{CredentialMediationRequirement/silent}}", return (failure, false).
1. Otherwise, if |accountsList|'s size is 1:
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. Set |account| to |accountsList|[0].
1. Set |accountState| to the result of running the [=compute the connection status=] algorithm
Expand Down Expand Up @@ -1005,6 +1050,21 @@ the exception thrown.
1. Let |credential| be the result of running the [=fetch an identity assertion=] algorithm with
|account|'s {{IdentityProviderAccount/id}}, |disclosureTextShown|, |isAutoSelected|,
|provider|, |config|, and |globalObject|.
1. If |credential| is an {{IdentityCredentialError}}:
1. The [=user agent=] MUST show UI to the user indicating that the identity assertion fetch
npm1 marked this conversation as resolved.
Show resolved Hide resolved
has failed.
1. The [=user agent=] MAY use the {{IdentityCredentialError/error}} in its UI to provide a
more specific error message to the user. For instance, if the value is one of
"invalid_request", "unauthorized_client", "access_denied", "server_error", and
"temporarily_unavailable", the [=user agent=] may note the reason in a user-friendly
manner. See [here](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1) for
more details about these.
1. The [=user agent=] MAY use the {{IdentityCredentialError/url}} to display a hyperlink
npm1 marked this conversation as resolved.
Show resolved Hide resolved
in its UI so the user can click on it to learn more information about the error message.
1. Wait until the UI is acknowledged or dismissed by the user.

Note: this wait means the [=user agent=] rejects the call only after the UI has been
closed by the user.
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. Return |credential|.
</div>

Expand Down Expand Up @@ -1175,7 +1235,7 @@ To <dfn>fetch the accounts</dfn> given an {{IdentityProviderAPIConfig}} |config|
{{IdentityProviderRequestOptions}} |provider|, and |globalObject|, run the following steps. This
returns an {{IdentityProviderAccountList}}.
1. Let |accountsUrl| be the result of [=computing the manifest URL=] given |provider|,
|config|["{{IdentityProviderAPIConfig/accounts_endpoint}}"], and |globalObject|.
|config|["{{IdentityProviderAPIConfig/accounts_endpoint}}"], true, and |globalObject|.
1. If |accountsUrl| is failure, return an empty list.
1. Let |request| be a new <a spec=fetch for=/>request</a> as follows:

Expand Down Expand Up @@ -1288,16 +1348,21 @@ To <dfn>fetch the account picture</dfn> given an {{IdentityProviderAccount}} |ac

The <a>fetch an identity assertion</a> algorithm is invoked after the user has granted permission to
use FedCM with a specific [=IDP=] account. It fetches the [=identity assertion endpoint=] to obtain
the token that will be provided to the [=RP=].
the token or error that will be provided to the [=RP=].

<div algorithm>
To <dfn>fetch an identity assertion</dfn> given a {{USVString}}
|accountId|, a boolean |disclosureTextShown|, a boolean |isAutoSelected|, an
{{IdentityProviderRequestOptions}} |provider|, an {{IdentityProviderAPIConfig}} |config|,
and |globalObject|, run the following steps. This returns an {{IdentityCredential}} or failure.
and |globalObject|, run the following steps. This returns an {{IdentityCredential}} or an
{{IdentityCredentialError}}.
1. Let |tokenUrl| be the result of [=computing the manifest URL=] given |provider|,
|config|["{{IdentityProviderAPIConfig/id_assertion_endpoint}}"], and |globalObject|.
1. If |tokenUrl| is failure, return failure.
|config|["{{IdentityProviderAPIConfig/id_assertion_endpoint}}"], true, and |globalObject|.
1. If |tokenUrl| is failure:
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. [=Queue a global task=] on the [=DOM manipulation task source=] given |globalObject| to
let |error| be a new {{IdentityCredentialError}} given |globalObject|'s
[=global object/realm=].
1. Wait for |error| to be set, and return it.
1. Let |requestBody| be the result of running [=urlencoded serializer=] with a list containing:
1. ("client_id", |provider|'s {{IdentityProviderConfig/clientId}})
1. ("nonce", |provider|'s {{IdentityProviderRequestOptions/nonce}})
Expand Down Expand Up @@ -1341,14 +1406,31 @@ To <dfn>fetch an identity assertion</dfn> given a {{USVString}}
set to the following steps given a <a spec=fetch for=/>response</a> |response| and |responseBody|:
1. Let |json| be the result of [=extract the JSON fetch response=] from |response| and
|responseBody|.
1. [=converted to an IDL value|Convert=] |json| to an {{IdentityProviderToken}}, |token|.
1. If one of the previous two steps threw an exception, set |credential| to failure
and return.
1. Let |credential| be a new {{IdentityCredential}} given |globalObject|'s
<a for="global object">realm</a>.
1. Set |credential|'s {{IdentityCredential/token}} to |token|.
1. Set |credential|'s {{IdentityCredential/isAutoSelected}} to
|isAutoSelected|.
1. If |json|[|token|] and |json|[|error|] both [=map/exist=] or both do not [=map/exist=],
set |credential| to a new {{IdentityCredentialError}} given |globalObject|'s
[=global object/realm=] and "IdentityCredentialError", and return.
1. If |json|[|token|] [=map/exists=]:
1. [=converted to an IDL value|Convert=] |json| to an {{IdentityProviderToken}}, |token|.
1. If one of the previous steps threw an exception, set |credential| to a new
{{IdentityCredentialError}} given |globalObject|'s [=global object/realm=]
and "IdentityCredentialError", and return.
1. Let |credential| be a new {{IdentityCredential}} given |globalObject|'s
[=global object/realm=].
1. Set |credential|'s {{IdentityCredential/token}} to |token|.
1. Set |credential|'s {{IdentityCredential/isAutoSelected}} to |isAutoSelected|.
1. Otherwise, i.e. if |json|[|error|] [=map/exists=]:
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. If |json|[|error|] is not an [=ordered map=], set |credential| to a new
{{IdentityCredentialError}} given |globalObject|'s [=global object/realm=] and
"IdentityCredentialError" and return.
npm1 marked this conversation as resolved.
Show resolved Hide resolved
1. [=converted to an IDL value|Convert=] |json| to an {{IdentityCredentialErrorInit}},
|errorInit|.
1. If |errorInit|.{{IdentityCredentialErrorInit/url}} is present:
1. Let |errorUrl| be the result of [=computing the manifest URL=] given |provider|,
|errorInit|.{{IdentityCredentialErrorInit/url}}, false, and |globalObject|.
1. If |errorUrl| is failure, set |errorInit|.{{IdentityCredentialErrorInit/url}}
to "".
1. Let |credential| be a new {{IdentityCredentialError}} given |globalObject|'s
[=global object/realm=], "IdentityCredentialError", and |errorInit|.
1. Wait for |credential| to be set.
1. Return |credential|.
</div>
Expand Down Expand Up @@ -1407,7 +1489,7 @@ To <dfn noexport>fetch the client metadata</dfn> given an {{IdentityProviderAPIC
an {{IdentityProviderRequestOptions}} |provider|, run the following steps. This returns an
{{IdentityProviderClientMetadata}} or failure.
1. Let |clientMetadataUrl| be the result of [=computing the manifest URL=] given |provider|,
|config|["{{IdentityProviderAPIConfig/client_metadata_endpoint}}"], and |globalObject|.
|config|["{{IdentityProviderAPIConfig/client_metadata_endpoint}}"], true, and |globalObject|.
1. If |clientMetadataUrl| is failure, return failure.
1. Let |request| be a new <a spec=fetch for=/>request</a> as follows:

Expand Down Expand Up @@ -1487,9 +1569,9 @@ To <dfn>fetch request</dfn> given a [=/request=] |request|, |globalObject|, and
</div>

<div algorithm>
When <dfn>computing the manifest URL</dfn> given an {{IdentityProviderRequestOptions}} |provider|, a
[=string=] |manifestString|, and |globalObject|, perform the following steps. This returns a
<a spec=url for=/>URL</a> or failure.
When <dfn>computing the manifest URL</dfn> given an {{IdentityProviderConfig}} |provider|, a
[=string=] |manifestString|, a boolean |requireSameOrigin|, and |globalObject|, perform the
following steps. This returns a <a spec=url for=/>URL</a> or failure.
1. Let |configUrl| be the result of running [=parse url=] with |provider|'s
{{IdentityProviderConfig/configURL}} and |globalObject|.
1. Let |manifestUrl| be the result of running [=parse url=] given |manifestString| (the relative
Expand All @@ -1500,7 +1582,10 @@ When <dfn>computing the manifest URL</dfn> given an {{IdentityProviderRequestOpt
allowed.

1. If |manifestUrl| is failure, return failure.
1. If |manifestUrl| is not [=same origin=] with |configUrl|, return failure.
1. If |requireSameOrigin| and |manifestUrl| is not [=same origin=] with |configUrl|, return
failure.
1. If |requireSameOrigin| is false and |manifestUrl|'s [=url/host=]'s [=host/registrable domain=]
is not equal to |configUrl|'s, return failure.
1. If |manifestUrl| is not a [=potentially trustworthy URL=], return failure.
1. Return |manifestUrl|.
</div>
Expand Down
Loading