Skip to content

Commit

Permalink
Add loginHint, RP context, and getUserInfo (#470)
Browse files Browse the repository at this point in the history
* rebase

* Feedback

* feedback

* Remove question mark

* Add quick note on need
  • Loading branch information
npm1 authored Jun 8, 2023
1 parent a2b9636 commit 47a2bca
Showing 1 changed file with 148 additions and 8 deletions.
156 changes: 148 additions & 8 deletions spec/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,20 @@ This specification introduces an extension to the {{CredentialRequestOptions}} o
The {{IdentityCredentialRequestOptions}} contains a list of
{{IdentityProviderConfig}}s that the [=RP=] supports and has
pre-registered with (i.e. the [=IDP=] has given the [=RP=] a `clientId`).
The {{IdentityCredentialRequestOptions}} also contains a {{IdentityCredentialRequestOptionsContext}}
which the user agent can use to provide a more meaningful dialog to users.

<xmp class=idl>
enum IdentityCredentialRequestOptionsContext {
"signin",
"signup",
"use",
"continue"
};

dictionary IdentityCredentialRequestOptions {
sequence<IdentityProviderConfig> providers;
IdentityCredentialRequestOptionsContext context = "signin";
};
</xmp>

Expand All @@ -427,6 +437,7 @@ dictionary IdentityProviderConfig {
required USVString configURL;
required USVString clientId;
USVString nonce;
DOMString loginHint;
};
</xmp>

Expand All @@ -439,6 +450,11 @@ dictionary IdentityProviderConfig {
:: A random number of the choice of the [=RP=]. It is generally used to associate a client
session with a {{IdentityProviderToken/token}} and to mitigate replay attacks. Therefore, this value should have
sufficient entropy such that it would be hard to guess.
: <b>{{IdentityProviderConfig/loginHint}}</b>
:: A string representing the login hint corresponding to an account which the RP wants the user
agent to show to the user. If provided, the user agent will not show accounts which do not
match this login hint value. It generally matches some attribute from the desired
{{IdentityProviderAccount}}.
</dl>

<!-- ============================================================ -->
Expand Down Expand Up @@ -502,7 +518,7 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu
before continuing.
1. Let |provider| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"][0].
1. Let |credential| be the result of running [=create an IdentityCredential=] with |provider|,
|options|["{{CredentialRequestOptions/mediation}}"], and |globalObject|.
|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
Expand Down Expand Up @@ -534,20 +550,25 @@ agent UI, and creates the {{IdentityCredential}} that is then returned to the [=

<div algorithm="create an IdentityCredential">
To <dfn>create an IdentityCredential</dfn> given an {{IdentityProviderConfig}}
|provider|, a {{CredentialRequestOptions/mediation}} |mediation|, and a
|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.
1. Assert: These steps are running [=in parallel=].
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.
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. Let |accountsList| be the result of [=fetch the accounts list=] with |config|, |provider|,
and |globalObject|.
1. If |provider|'s {{IdentityProviderConfig/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
{{IdentityProviderConfig/loginHint}}.
1. If |accountsList| is failure, return (failure, false).
1. For each |acc| in |accountsList|:
1. If |acc|["{{IdentityProviderAccount/picture}}"] is present, [=fetch the account picture=]
Expand Down Expand Up @@ -580,7 +601,8 @@ the exception thrown.
with |account|, |accountState|, |config|, |provider|, and |globalObject|. Also set
|disclosureTextShown| to true.
1. Otherwise, show a dialog to request user permission to sign in via |account|, and set the
result in |permission|.
result in |permission|. The user agent MAY use |options|'s
{{IdentityCredentialRequestOptions/context}} to customize the dialog.
1. Otherwise:
1. Set |account| to the result of running the [=select an account=] from the
|accountsList|.
Expand Down Expand Up @@ -816,6 +838,7 @@ dictionary IdentityProviderAccount {
USVString given_name;
USVString picture;
sequence<USVString> approved_clients;
sequence<DOMString> login_hints;
};
dictionary IdentityProviderAccountList {
sequence<IdentityProviderAccount> accounts;
Expand Down Expand Up @@ -970,6 +993,8 @@ an {{IdentityProviderAPIConfig}} |config|, an {{IdentityProviderConfig}} |provid
is defined, and the |provider|'s {{IdentityProviderConfig/clientId}} is not in the list of
|account|["{{IdentityProviderAccount/approved_clients}}"], then the user agent MUST display
the |metadata|["{{IdentityProviderClientMetadata/terms_of_service_url}}"] link.
1. The user agent MAY use the {{IdentityCredentialRequestOptions/context}} to customize the
dialog shown.
1. If the user does not grant permission, return false.
1. [=Create a connection between the RP and the IdP account=] with |provider|, |account|, and
|globalObject|.
Expand Down Expand Up @@ -1094,6 +1119,116 @@ and a |responseBody|, run the following steps. This returns an [=ordered map=].
1. Return |json|.
</div>

<!-- ============================================================ -->
## The IdentityProvider Interface ## {#browser-api-identity-provider-interface}
<!-- ============================================================ -->

This specification introduces the {{IdentityUserInfo}} dictionary as well as the
{{IdentityProvider}} interface:

<pre class="idl">
dictionary IdentityUserInfo {
USVString email;
USVString name;
USVString givenName;
USVString picture;
};

[Exposed=Window, SecureContext] interface IdentityProvider {
static Promise&lt;sequence&lt;IdentityUserInfo&gt;&gt; getUserInfo(IdentityProviderConfig config);
};
</pre>

Issue: [Decide](https://github.com/fedidcg/FedCM/issues/476) whether {{IdentityProvider}} is the
correct location for the {{IdentityProvider/getUserInfo()}} method.

An {{IdentityUserInfo}} represents user account information from a user. This information is exposed
to the [=IDP=] once the user has already used the FedCM API to login in the [=RP=]. That is, it is
exposed when there exists an account |account| such that the [=connected accounts set=] [=list/contains=]
the triple ([=RP=], [=IDP=], |account|). The information matches what is received from the
<a>accounts list endpoint</a>. The [=IDP=] can obtain this information by invoking the
{{IdentityProvider/getUserInfo()}} static method from an iframe matching the [=/origin=] of its
{{IdentityProviderConfig/configURL}}.

<div class="example">
```js
const userInfo = await IdentityProvider.getUserInfo({
configUrl: "https://idp.example/fedcm.json",
clientId: "client1234"
});

if (userInfo.length > 0) {
// It's up to the IDP regarding how to display the returned accounts.
const name = userInfo[0].name;
const givenName = userInfo[0].givenName;
const displayName = givenName ? givenName : name;
const picture = userInfo[0].picture;
const email = userInfo[0].email;
}
```
</div>

<div algorithm="getUserInfo">
When invoking the {{IdentityProvider/getUserInfo()}} method given an {{IdentityProviderConfig}}
|provider|, perform the following steps:

1. Let |globalObject| be the [=current global object=].
1. Let |document| be |globalObject|'s [=associated Document=].
1. If |document| is not [=allowed to use=] the [=identity-credentials-get=]
[=policy-controlled feature=], throw a "{{NotAllowedError}}" {{DOMException}}.
1. If there does not exist an account |account| such that [=compute the connection status=] of
|provider|, |account|, and |globalObject| returns
[=compute the connection status/connected=], then throw a new "{{NetworkError}}"
{{DOMException}}. This check can be performed by iterating over the
[=connected accounts set=] or by keeping a separate data structure to make this lookup fast.
1. Let |configUrl| be the result of running [=parse url=] with |provider|'s
{{IdentityProviderConfig/configURL}} and |globalObject|.
1. If |configUrl| is failure, throw an "{{InvalidStateError}}" {{DOMException}}.
1. If |document|'s [=Document/origin=] is not [=same origin=] as |configUrl|'s [=url/origin=],
throw an "{{InvalidStateError}}" {{DOMException}}.
1. Run a [[!CSP]] check with a [[CSP#directive-connect-src|connect-src]] directive on the URL
passed as |configUrl|. If it fails, throw a new "{{NetworkError}}" {{DOMException}}.
1. If |globalObject|'s [=Window/navigable=] is a [=/top-level traversable=], throw a new
"{{NetworkError}}" {{DOMException}}.
1. If the user has disabled the FedCM API on the |globalObject|'s [=Window/navigable=]'s
[=navigable/top-level traversable=], throw a new "{{NetworkError}}" {{DOMException}}.
1. Let |promise| be a new {{Promise}}.
1. Perform the following steps [=in parallel=]:
1. Let |config| be the result of running [=fetch the config file=] with |provider| and
|globalObject|.
1. If |config| is failure, [=reject=] |promise| with a new "{{NetworkError}}"
{{DOMException}}.
1. Let |accountsList| be the result of [=fetch the accounts list=] with |config|, |provider|,
and |globalObject|.
1. Let |hasReturningAccount| be false.
1. For each |account| in |accountsList|:
1. If |account|["{{IdentityProviderAccount/approved_clients}}"] is not empty and it does not
[=list/contain=] |provider|'s {{IdentityProviderConfig/clientId}}, continue.

Note: this allows the [=IDP=] to override whether an account is a returning account.
This could be useful for instance in cases where the user has revoked the account
out of band.

1. [=Compute the connection status=] of |provider|, |account|, and |globalObject|. If the
result is [=compute the connection status/connected=], set |hasReturningAccount| to
true.
1. If |hasReturningAccount| is false, [=reject=] |promise| with a new "{{NetworkError}}"
{{DOMException}}.
1. Let |userInfoList| be a new [=list=].
1. For each |account| in |accountsList|:
1. [=list/Append=] an {{IdentityUserInfo}} to |userInfoList| with the following values:

: {{IdentityUserInfo/email}}
:: |account|["{{IdentityProviderAccount/email}}"]
: {{IdentityUserInfo/name}}
:: |account|["{{IdentityProviderAccount/name}}"]
: {{IdentityUserInfo/givenName}}
:: |account|["{{IdentityProviderAccount/given_name}}"]
: {{IdentityUserInfo/picture}}
:: |account|["{{IdentityProviderAccount/picture}}"]
1. [=Resolve=] a new {{Promise}} with |userInfoList|.
</div>

<!-- ============================================================ -->
# Identity Provider HTTP API # {#idp-api}
<!-- ============================================================ -->
Expand Down Expand Up @@ -1309,6 +1444,10 @@ Every {{IdentityProviderAccount}} is expected to have members with the following
:: A list of [=RP=]s (that gets matched against the requesting {{IdentityProviderConfig/clientId}}) this account is already registered with.
Used in the [=request permission to sign-up=] to allow the [=IDP=] to control whether to show
the Privacy Policy and the Terms of Service.
: <dfn>login_hints</dfn>
:: A list of strings which correspond to all of the login hints which match with this account.
An [=RP=] can use the {{IdentityProviderConfig/loginHint}} to request that only an account
matching a given value is shown to the user.
</dl>

For example:
Expand All @@ -1322,14 +1461,16 @@ For example:
"name": "John Doe",
"email": "john_doe@idp.example",
"picture": "https://idp.example/profile/123",
"approved_clients": ["123", "456", "789"]
"approved_clients": ["123", "456", "789"],
"login_hints": ["john_doe"]
}, {
"id": "5678",
"given_name": "Johnny",
"name": "Johnny",
"email": "johnny@idp.example",
"picture": "https://idp.example/profile/456"
"approved_clients": ["abc", "def", "ghi"]
"picture": "https://idp.example/profile/456",
"approved_clients": ["abc", "def", "ghi"],
"login_hints": ["email=johhny@idp.example", "id=5678"]
}]
}
```
Expand Down Expand Up @@ -2111,8 +2252,7 @@ path: img/mock5.svg

Secondary use is the use of collected information about an individual without the individual's
perimssion for a purpose different from that for which the information was collected. This attack
happens when [=IDP=]s misuse the the information collected to enable sign-in for other
purposes.
happens when [=IDP=]s misuse the information collected to enable sign-in for other purposes.

Existing federation protocols require that the [=IDP=] know which service is requesting a token
in order to allow identity federation. Identity providers can use this fact to build profiles of
Expand Down

0 comments on commit 47a2bca

Please sign in to comment.