diff --git a/spec/index.bs b/spec/index.bs
index d640c3adb..6a3d510cf 100644
--- a/spec/index.bs
+++ b/spec/index.bs
@@ -442,6 +442,7 @@ This specification introduces a new type of {{Credential}}, called an {{Identity
static Promise<undefined> disconnect(IdentityCredentialDisconnectOptions options);
readonly attribute USVString? token;
readonly attribute boolean isAutoSelected;
+ readonly attribute USVString configURL;
};
@@ -455,6 +456,9 @@ This specification introduces a new type of {{Credential}}, called an {{Identity
:: {{IdentityCredential/isAutoSelected}}'s attribute getter returns the value it is
set to. It represents whether the user's identity credential was automatically selected when
going through the UI flow which resulted in this {{IdentityCredential}}.
+ : {{IdentityCredential/configURL}}
+ :: The {{IdentityCredential/configURL}}'s attribute getter returns the value it is set to.
+ It represents the config URL corresponding to the [=IDP=] which issued this credential.
: {{Credential/[[type]]}}
:: The {{IdentityCredential}}'s {{Credential/[[type]]}}'s value is "identity".
: {{Credential/[[discovery]]}}
@@ -729,20 +733,22 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu
{{IdentityCredential}} (or throws an error to the caller).
1. Assert: These steps are running [=in parallel=].
- 1. If the [=list/size=] of
+ 1. If
|options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"]
- is not equal to 1, [=queue a global task=] on the [=DOM manipulation task source=] given
- |globalObject| to throw a new "{{NetworkError}}" {{DOMException}}
+ is [=list/empty=], [=queue a global task=] on the [=DOM manipulation task source=] given
+ |globalObject| to throw a new "{{NetworkError}}" {{DOMException}}.
Note: The |globalObject| is not currently passed onto the
{{Credential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)}}
algorithm. See issue.
+ 1. Let |providerList| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"].
+ 1. Let |credential| be the result of running [=create an IdentityCredential=] with |providerList|,
+ |options|, and |globalObject|.
+ Note: The |globalObject| is not currently passed onto the
+ {{Credential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)}}
+ algorithm. See issue.
- Issue: Support choosing accounts from multiple [=IDP=]s, as described [here](https://github.com/fedidcg/FedCM/issues/319).
- 1. Let |provider| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"][0].
- 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. The user agent SHOULD wait a random amount of time
@@ -773,23 +779,24 @@ The create an IdentityCredential algorithm invokes the various FedCM fetc
agent UI, and creates the {{IdentityCredential}} that is then returned to the [=RP=].
-To
create an IdentityCredential given an {{IdentityProviderRequestOptions}}
-|provider|, a {{CredentialRequestOptions}} |options|, and a
+To
create an IdentityCredential given a [=sequence=] of {{IdentityProviderRequestOptions}}
+|providerList|, 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.
1. Assert: These steps are running [=in parallel=].
1. Let |mode| be |options|'s {{IdentityCredentialRequestOptions/mode}}.
1. If |mode| is {{IdentityCredentialRequestOptionsMode/active}}:
+ 1. If |providerList|'s [=list/size=] is not equal to 1, return (failure, true).
1. Assert: |globalObject| is a {{Window}}.
1. If |globalObject| does not have [=transient activation=], return (failure, true).
1. Otherwise, if there is a pending request where {{IdentityCredentialRequestOptionsMode}} is
{{IdentityCredentialRequestOptionsMode/passive}} on |globalObject|'s [=Window/navigable=]'s
[=navigable/top-level traversable=] or on any of its descendants, reject the pending
request with a "{{NetworkError}}" {{DOMException}}.
- 1. The user agent MAY wait an arbitrary amount of time before continuing with the
- next steps. The user agent MAY also return (failure, false) at this point or
- after some arbitrary wait.
+ 1. The user agent MAY wait an arbitrary amount of time before continuing with the next steps.
+ The user agent MAY also return (failure, false) at this point or after some arbitrary wait.
+ The user agent MAY also set |providerList| to a subset of itself after some arbitrary wait.
NOTE: For example, the user agent could do any of the following:
@@ -800,51 +807,114 @@ the exception thrown.
* Use previous user behavior to determine not to show any
UI to the user and silently reject the request.
- 1. Let |loginStatus| be the result of [=get the login status=] with
- the [=/origin=] of |provider|'s {{IdentityProviderConfig/configURL}}.
- 1. If |loginStatus| is [=unknown=], a user agent MAY set it to [=logged-out=].
- 1. If |loginStatus| is [=logged-out=]:
- 1. If |mode| is {{IdentityCredentialRequestOptionsMode/active}}:
- 1. Let |result| be the result of running
- [=fetch the config file and show an IDP login dialog=] with
- |provider| and |globalObject|.
- 1. If |result| is failure, return (failure, true).
- 1. Otherwise, the user agent MUST do one of the following:
+ 1. Let |mediation| be |options|'s {{CredentialRequestOptions/mediation}}.
+ 1. Let |providerMap| be a new [=map=].
+ 1. For each |provider| in |providerList|:
+ Note: The fetches performed in these steps to populate |providerMap| can be performed at the
+ same time, e.g., the second [=IDP=]'s fetches can be started before the first ones finish,
+ since these are indepedent.
+ 1. Let |providerOrigin| be the [=/origin=] of |provider|'s {{IdentityProviderConfig/configURL}}.
+ 1. If |providerMap| [=map/contains=] |providerOrigin|, return (failure, true).
+ 1. Let |loginStatus| be the result of [=get the login status=] with |providerOrigin|.
+ 1. If |loginStatus| is [=unknown=], a user agent MAY set it to [=logged-out=].
+ 1. If |loginStatus| is [=logged-out=]:
+ 1. If |mode| is {{IdentityCredentialRequestOptionsMode/active}}:
+ 1. Let |result| be the result of running
+ [=fetch the config file and show an IDP login dialog=] with
+ |provider| and |globalObject|.
+ 1. If |result| is failure, return (failure, true).
+ 1. Otherwise, set |providerMap|[|providerOrigin|] to "logged-out" and [=continue=].
+ 1. Let |requiresUserMediation| be |providerOrigin|'s [=requires user mediation=].
+ 1. If |requiresUserMediation| is true and |mediation| is
+ "{{CredentialMediationRequirement/silent}}", [=continue=].
+ 1. Let |config| be the result of running [=fetch the config file=] with
+ |provider| and |globalObject|.
+ 1. If |config| is failure, [=continue=].
+ 1.
Fetch accounts step: Let |accountsList| be the result of
+ [=fetch the accounts=] with |config|, |provider|, and |globalObject|.
+ 1. If |accountsList| is failure, or the size of |accountsList| is 0:
+ 1. [=Set the login status=] of |providerOrigin| to [=logged-out=].
+ A user agent may decide to skip this step if no credentials were
+ sent to server.
+
+ Note: For example, if the fetch failed due to a DNS error, no
+ credentials were sent and therefore the [=IDP=] did not learn
+ the user's identity. In this situation, we do not know whether
+ the user is signed in or not and so we may not want to reset
+ the status.
+ 1. If |loginStatus| is [=logged-in=], set |providerMap|[|providerOrigin|] to "mismatch"
+ and [=continue=].
+ 1. Assert: |accountsList| is not failure and the size of |accountsList| is not 0.
+ 1. [=Set the login status=] for |providerOrigin| to [=logged-in=].
+ 1. For each |acc| in |accountsList|:
+ 1. If |acc|["{{IdentityProviderAccount/picture}}"] is present, [=fetch the account picture=]
+ with |acc| and |globalObject|. If the [=user agent=] displays this picture to
+ the user at any point, it MUST reuse the result of this fetch instead of redownloading
+ the picture.
+
+ Note: We require downloading the pictures here before we potentially filter the account
+ list so that the identity provider cannot determine what hints were provided
+ based on which fetches occurred.
+ 1. If |provider|'s {{IdentityProviderRequestOptions/loginHint}} is not empty:
+ 1. For every |account| in |accountList|, remove |account| from |accountList| if |account|'s
+ {{IdentityProviderAccount/login_hints}} does not [=list/contain=] |provider|'s
+ {{IdentityProviderRequestOptions/loginHint}}.
+ 1. If |accountList| is now empty, set |providerMap|[|providerOrigin|] to "mismatch" and
+ continue.
+ 1. If |provider|'s {{IdentityProviderRequestOptions/domainHint}} is not empty:
+ 1. For every |account| in |accountList|:
+ 1. If {{IdentityProviderRequestOptions/domainHint}} is "any":
+ 1. If |account|'s {{IdentityProviderAccount/domain_hints}} is empty, remove
+ |account| from |accountList|.
+ 1. Otherwise, remove |account| from |accountList| if |account|'s
+ {{IdentityProviderAccount/domain_hints}} does not [=list/contain=] |provider|'s
+ {{IdentityProviderRequestOptions/domainHint}}.
+ 1. If |accountList| is now empty, set |providerMap|[|providerOrigin|] to "mismatch" and
+ continue.
+ 1. If |config|.{{IdentityProviderAPIConfig/account_label}} is present:
+ 1. For every |account| in |accountList|, remove |account| from |accountList| if |account|'s
+ {{IdentityProviderAccount/label_hints}} does not [=list/contain=]
+ |config|.{{IdentityProviderAPIConfig/account_label}}.
+ 1. If |accountList| is now empty, set |providerMap|[|providerOrigin|] to "mismatch" and
+ continue.
+ 1. Set |providerMap|[|providerOrigin|] to |accountsList|.
+ 1. If |providerMap| [=map/is empty=], return (failure, false).
+ 1. Let |registeredAccount| and |numRegisteredAccounts| be null and 0, respectively.
+ 1. Let |selectedAccount| be null.
+ 1. For each (|providerOrigin|, |value|) in |providerMap|:
+ 1. If |value| is not a [=list=] |accountsList|, [=continue=].
+ 1. For each |acc| in |accountsList|:
+ 1. If |acc| is [=eligible for auto reauthentication=] given the relevant |provider|, and
+ |globalObject|, set |registeredAccount| to |acc|, increase |numRegisteredAccounts| by 1,
+ and set |requiresUserMediation| be |providerOrigin|'s [=requires user mediation=].
+ 1. If |mediation| is not "{{CredentialMediationRequirement/required}}", |requiresUserMediation|
+ is false, |numRegisteredAccounts| is equal to 1, and |providerMap|'s [=map/values=] do not
+ [=map/contain=] "mismatch":
+ 1. Set |selectedAccount| to |registeredAccount| and |permission| to true. When doing this, the user
+ agent MAY show some UI to the user indicating that they are being
+
auto-reauthenticated.
+ 1. Set |isAutoSelected| to true.
+ 1. Otherwise, if |mediation| is "{{CredentialMediationRequirement/silent}}" and |providerMap|'s
+ [=map/values=] do not [=map/contain=] "mismatch", return (failure, true).
+ 1. Let |permission|, |permissionRequested|, and |isAutoSelected| be set to false.
+ 1. Let |allAccounts| be an empty [=list=].
+ 1. Build UI by adding the following for each (|providerOrigin|, |value|) in |providerMap|:
+ 1. If |value| is "logged-out", the user agent adds one of the following:
+ * Nothing: no UI is shown regarding this [=IDP=].
+ * Prompt to continue with this [=IDP=]. If the user continues, the user
+ agent SHOULD set the login status to [=unknown=]. This MAY include an
* Return (failure, false).
* Prompt the user whether to continue. If the user continues, the user
agent SHOULD set |loginStatus| to [=unknown=]. This MAY include an
affordance to [=show an IDP login dialog=].
- * If the user cancels this dialog, return (failure, true).
* If the user triggers this affordance:
1. Let |result| be the result of running
[=fetch the config file and show an IDP login dialog=]
- with |provider| and |globalObject|.
+ with the relevant |provider| and |globalObject|.
1. If |result| is failure, return (failure, true).
- 1. Let |requiresUserMediation| be |provider|'s {{IdentityProviderConfig/configURL}}'s [=/origin=]'s
- [=requires user mediation=].
- 1. Let |mediation| be |options|'s {{CredentialRequestOptions/mediation}}.
- 1. If |requiresUserMediation| is true and |mediation| is
- "{{CredentialMediationRequirement/silent}}", return (failure, true).
- 1. Let |config| be the result of running [=fetch the config file=] with
- |provider| and |globalObject|.
- 1. If |config| is failure, return (failure, false).
- 1.
Fetch accounts step: Let |accountsList| be the result of
- [=fetch the accounts=] with |config|, |provider|, and |globalObject|.
- 1. If |accountsList| is failure, or the size of |accountsList| is 0:
- 1. [=Set the login status=] for the [=/origin=] of the
- {{IdentityProviderConfig/configURL}} to [=logged-out=].
- A user agent may decide to skip this step if no credentials were
- sent to server.
-
- Note: For example, if the fetch failed due to a DNS error, no
- credentials were sent and therefore the [=IDP=] did not learn
- the user's identity. In this situation, we do not know whether
- the user is signed in or not and so we may not want to reset
- the status.
- 1.
Mismatch dialog step: If |loginStatus| is [=logged-in=], show a
- dialog to the user. The contents of this dialog are defined by the user
- agent. This dialog SHOULD provide an affordance for the user to trigger
- the [=show an IDP login dialog=] algorithm with |config| and |provider|;
+ 1. Otherwise, if |value| is "mismatch", add contents indicating this |providerOrigin|
+ to the user. The contents SHOULD provide an affordance for the user to trigger
+ the [=show an IDP login dialog=] algorithm with the relevant |config| and |provider|;
this dialog is the
confirm IDP login dialog.
Note: This situation happens when the browser expects the user
@@ -855,94 +925,55 @@ the exception thrown.
impossible by always showing UI of some kind when credentials
were sent to the server.
- 1. Wait until one of the following occurs:
-
- * If the user closes the dialog, return (failure, true).
-
- * If the [=show an IDP login dialog=] algorithm was triggered:
-
- 1. Let |result| be the result of that algorithm.
- 1. If |result| is failure, return (failure, true). The user
- agent MAY show a dialog to the user before or after
- returning failure indicating this failure.
- 1. Otherwise, go back to the [=fetch accounts step=].
-
- 1. Assert: |accountsList| is not failure and the size of |accountsList| is not 0.
- 1. [=Set the login status=] for the [=/origin=] of the
- {{IdentityProviderConfig/configURL}} to [=logged-in=].
- 1. For each |acc| in |accountsList|:
- 1. If |acc|["{{IdentityProviderAccount/picture}}"] is present, [=fetch the account picture=]
- with |acc| and |globalObject|. If the [=user agent=] displays this picture to
- the user at any point, it MUST reuse the result of this fetch instead of redownloading
- the picture.
-
- Note: We require downloading the pictures here before we potentially filter the account
- list so that the identity provider cannot determine what hints were provided
- based on which fetches occurred.
- 1. If |provider|'s {{IdentityProviderRequestOptions/loginHint}} is not empty:
- 1. For every |account| in |accountList|, remove |account| from |accountList| if |account|'s
- {{IdentityProviderAccount/login_hints}} does not [=list/contain=] |provider|'s
- {{IdentityProviderRequestOptions/loginHint}}.
- 1. If |accountList| is now empty, go to the [=mismatch dialog step=].
- 1. If |provider|'s {{IdentityProviderRequestOptions/domainHint}} is not empty:
- 1. For every |account| in |accountList|:
- 1. If {{IdentityProviderRequestOptions/domainHint}} is "any":
- 1. If |account|'s {{IdentityProviderAccount/domain_hints}} is empty, remove
- |account| from |accountList|.
- 1. Otherwise, remove |account| from |accountList| if |account|'s
- {{IdentityProviderAccount/domain_hints}} does not [=list/contain=] |provider|'s
- {{IdentityProviderRequestOptions/domainHint}}.
- 1. If |accountList| is now empty, go to the [=mismatch dialog step=].
- 1. If |config|.{{IdentityProviderAPIConfig/account_label}} is present:
- 1. For every |account| in |accountList|, remove |account| from |accountList| if |account|'s
- {{IdentityProviderAccount/label_hints}} does not [=list/contain=]
- |config|.{{IdentityProviderAPIConfig/account_label}}.
- 1. If |accountList| is now empty, go to the [=mismatch dialog step=].
- 1. Let |registeredAccount|, |numRegisteredAccounts| be null and 0, respectively.
- 1. Let |account| be null.
- 1. For each |acc| in |accountsList|:
- 1. If |acc| is [=eligible for auto reauthentication=] given |provider|, and |globalObject|,
- set |registeredAccount| to |acc| and increase |numRegisteredAccounts| by 1.
- 1. Let |permission|, |permissionRequested|, and |isAutoSelected| be set to false.
- 1. If |mediation| is not "{{CredentialMediationRequirement/required}}", |requiresUserMediation|
- is false, and |numRegisteredAccounts| is equal to 1:
- 1. Set |account| to |registeredAccount| and |permission| to true. When doing this, the user
- agent MAY show some UI to the user indicating that they are being
-
auto-reauthenticated.
- 1. Set |isAutoSelected| to true.
- 1. Otherwise, if |mediation| is "{{CredentialMediationRequirement/silent}}", return (failure, true).
- 1. Otherwise, if |accountsList|'s size is 1:
- 1. Set |account| to |accountsList|[0].
- 1. If [=compute the connection status=] of |account|, |provider|, and |globalObject| returns
- [=compute the connection status/connected=], show a dialog to request user permission to sign
- in via |account|, and set the result in |permission|. The user agent MAY use |options|'s
- {{IdentityCredentialRequestOptions/context}} and |options|'s
- {{IdentityCredentialRequestOptions/mode}} to customize the dialog.
- 1. Otherwise, let |permission| be the result of running [=request permission to sign-up=]
- algorithm with |account|, |config|, |provider|, and |globalObject|. Also set
- |permissionRequested| to true if the user agent [=supports showing a permission prompt=].
- 1. Otherwise:
- 1. Set |account| to the result of running the [=select an account=] from the
- |accountsList|.
- 1. If |account| is failure, return (failure, true).
- 1. If [=compute the connection status=] of |account|, |provider| and |globalObject| is
- [=compute the connection status/connected=], set |permission| to true.
- 1. Otherwise, if |provider|.{{IdentityProviderRequestOptions/fields}} is [=list/empty=],
- [=create a connection between the RP and the IdP account=] with |provider|, |account|,
- and |globalObject|, and set |permission| to true.
-
- Note: The connection would normally be created in the [=request permission to sign-up=]
- algorithm, but we do not want to show an extra dialog in this case.
+ * If the [=show an IDP login dialog=] algorithm was triggered:
+
+ 1. Let |result| be the result of that algorithm.
+ 1. If |result| is failure, return (failure, true). The user
+ agent MAY show a dialog to the user before or after
+ returning failure indicating this failure.
+ 1. Otherwise, go back to the [=fetch accounts step=] to get an updated
+ value of |providerMap| for this [=IDP=].
+ 1. Otherwise, |value| is a [=list=] of accounts. [=list/Extend=] |allAccounts| with |value|.
+ 1. Also include a UI affordance to close the dialog. If the user closes this dialog, return (failure,
+ true).
+ 1. If |allAccounts| is not [=list/empty=], also add UI to present the account options to the user as follows:
+ 1. If |allAccounts|'s size is 1 and |providerMap|'s [=map/values=] do not [=map/contain=]
+ "mismatch":
+ 1. Set |selectedAccount| to |allAccounts|[0].
+ 1. If [=compute the connection status=] of |selectedAccount|, the relevant |provider|,
+ and |globalObject| returns [=compute the connection status/connected=], show a
+ dialog to request user permission to sign in via |selectedAccount|, and set the
+ result in |permission|. The user agent MAY use |options|'s
+ {{IdentityCredentialRequestOptions/context}} and |options|'s
+ {{IdentityCredentialRequestOptions/mode}} to customize the dialog.
+ 1. Otherwise, set |permission| to the result of running [=request permission to sign-up=]
+ algorithm with |selectedAccount|, the relevant |config|, the relevant |provider|,
+ and |globalObject|. Also set |permissionRequested| to true if the user agent
+ [=supports showing a permission prompt=].
1. Otherwise:
- 1. Let |permission| be the result of running the [=request permission to sign-up=]
- algorithm with |account|, |config|, |provider|, and |globalObject|.
- 1. Set |permissionRequested| to true.
- 1. Wait until the [=user agent=]'s dialogs requesting for user choice or permission to be
- closed, if any are created in the previous steps.
- 1. Assert: |account| is not null.
+ 1. Show UI to allow the user to select an account chooser displaying the options from
+ |accountsList|.
+ 1. If the user selects an account, perform the following steps:
+ 1. Set |selectedAccount| to the chosen {{IdentityProviderAccount}}.
+ 1. If [=compute the connection status=] of |selectedAccount|, the relevant |provider|,
+ and |globalObject| is [=compute the connection status/connected=], set |permission|
+ to true.
+ 1. Otherwise, if |provider|.{{IdentityProviderRequestOptions/fields}} is [=list/empty=],
+ [=create a connection between the RP and the IdP account=] with |provider|, |account|,
+ and |globalObject|, and set |permission| to true.
+ Note: The connection would normally be created in the [=request permission to sign-up=]
+ algorithm, but we do not want to show an extra dialog in this case.
+ 1. Otherwise:
+ 1. Set |permission| to the result of running the [=request permission to sign-up=]
+ algorithm with |selectedAccount|, the relevant |config|, the relevant |provider|,
+ and |globalObject|.
+ 1. Set |permissionRequested| to true.
+ 1. If the [=user agent=] created any dialogs requesting user choice or permission in the previous
+ steps, wait until they are closed.
1. If |permission| is false, then return (failure, true).
+ 1. Assert: |selectedAccount| is not null.
1. Let |credential| be the result of running the [=fetch an identity assertion=] algorithm with
- |account|'s {{IdentityProviderAccount/id}}, |permissionRequested|, |isAutoSelected|,
+ |selectedAccount|'s {{IdentityProviderAccount/id}}, |permissionRequested|, |isAutoSelected|,
|provider|, |config|, and |globalObject|.
1. Return |credential|.
@@ -1354,6 +1385,8 @@ To