diff --git a/.github/workflows/build-validate-publish.yaml b/.github/workflows/build-validate-publish.yaml index 7d089db9c..243daff31 100644 --- a/.github/workflows/build-validate-publish.yaml +++ b/.github/workflows/build-validate-publish.yaml @@ -7,7 +7,7 @@ on: jobs: run: name: Build, Validate, and Publish - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: w3c/spec-prod@v2 diff --git a/spec/index.bs b/spec/index.bs index 73d276f97..204ba8041 100644 --- a/spec/index.bs +++ b/spec/index.bs @@ -451,7 +451,7 @@ This specification introduces a new type of {{Credential}}, called an {{Identity :: The {{Credential/id}}'s attribute getter returns the empty string. : {{IdentityCredential/token}} :: The {{IdentityCredential/token}}'s attribute getter returns the value it is set to. - It represents the minted {{IdentityProviderToken/token}} provided by the [=IDP=]. + It represents the minted {{IdentityAssertionResponse/token}} provided by the [=IDP=]. : {{IdentityCredential/isAutoSelected}} :: {{IdentityCredential/isAutoSelected}}'s attribute getter returns the value it is set to. It represents whether the user's identity credential was automatically selected when @@ -623,8 +623,11 @@ 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. +The {{IdentityCredentialRequestOptions}} also contains an +{{IdentityCredentialRequestOptionsContext}}, which the user agent can use to +provide a more meaningful dialog to users, and an +{{IdentityCredentialRequestOptionsMode}}, which the user agent can use to +specify different behaviors or dialog types. enum IdentityCredentialRequestOptionsContext { @@ -634,9 +637,15 @@ enum IdentityCredentialRequestOptionsContext { "continue" }; +enum IdentityCredentialRequestOptionsMode { + "active", + "passive" +}; + dictionary IdentityCredentialRequestOptions { required sequence<IdentityProviderRequestOptions> providers; IdentityCredentialRequestOptionsContext context = "signin"; + IdentityCredentialRequestOptionsMode mode = "passive"; }; @@ -665,8 +674,8 @@ dictionary IdentityProviderRequestOptions : IdentityProviderConfig { :: The {{id_assertion_endpoint_request/client_id}} provided to the [=RP=] out of band by the [=IDP=] : {{IdentityProviderRequestOptions/nonce}} :: 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. + session with a {{IdentityAssertionResponse/token}} and to mitigate replay attacks. + Therefore, this value should have sufficient entropy such that it would be hard to guess. : {{IdentityProviderRequestOptions/loginHint}} :: 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 @@ -776,27 +785,34 @@ To create an IdentityCredential given an {{IdentityProviderRequestOpt 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 [=active=]: + 1. Let |W| be |globalObject|'s [=associated Window=]. + 1. If |W| does not have [=transient activation=], return (failure, true). + 1. Otherwise, if there is a pending request where |mode| is [=passive=] + on |W|'s [=Window/navigable=]'s [=navigable/top-level traversable=] + or on any of its descendants, reject the pending request with a + "{{NetworkError}}" {{DOMException}}. 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=], the user agent MUST do one of the following: - - * 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 |config| be the result of running [=fetch the config file=] - with |provider| and |globalObject|. - 1. If |config| is failure, return (failure, true). - 1. [=Show an IDP login dialog=] with |config| and |provider|. - 1. If that algorithm returns failure, return (failure, true). - - Issue: We should perhaps provide a way to let the [=RP=] request that - the second option is provided, possibly gated on a user gesture. - See [this issue](https://github.com/fedidcg/FedCM/issues/442) for discussion. + 1. If |loginStatus| is [=logged-out=]: + 1. If |mode| is [=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: + * 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|. + 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}}. @@ -886,7 +902,8 @@ the exception thrown. 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}} to customize the dialog. + {{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. @@ -1270,12 +1287,33 @@ To fetch an identity assertion given a {{USVString}} set to the following steps given a response |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. [=converted to an IDL value|Convert=] |json| to an {{IdentityAssertionResponse}}, |token|. 1. If one of the previous two steps threw an exception, set |credential| to failure and return. + 1. If neither {{IdentityAssertionResponse/token}} nor + {{IdentityAssertionResponse/continue_on}} was specified, set |credential| to failure + and return. + 1. If {{IdentityAssertionResponse/token}} was specified, let |tokenString| + be |token|'s {{IdentityAssertionResponse/token}}. + 1. Otherwise, run these steps [=in parallel=]: + 1. Let |continueOnUrl| be the result of running [=parse url=] with |token|'s + {{IdentityAssertionResponse/continue_on}} and |globalObject|. + 1. If |continueOnUrl| is failure, set |credential| to failure and return. + 1. If |continueOnUrl| is not [=same origin=] with |tokenUrl|, set |credential| + to failure and return. + 1. Let |tokenPair| be the result of [=show a continuation dialog=] with |continueOnUrl|. + 1. If |tokenPair| is failure, set |credential| to failure and return. + 1. Let |tokenString| be the first entry of |tokenPair|. + 1. If the second entry of |tokenPair| is not null, set |accountId| to that second entry. + 1. Wait for |tokenString| or |credential| to be set. + 1. If |credential| is set: + 1. Assert that |credential| is set to failure. + 1. Return |credential|. + 1. [=Create a connection between the RP and the IdP account=] with |provider|, |accountId|, and + |globalObject|. 1. Let |credential| be a new {{IdentityCredential}} given |globalObject|'s realm. - 1. Set |credential|'s {{IdentityCredential/token}} to |token|. + 1. Set |credential|'s {{IdentityCredential/token}} to |tokenString|. 1. Set |credential|'s {{IdentityCredential/isAutoSelected}} to |isAutoSelected|. 1. Wait for |credential| to be set. @@ -1283,8 +1321,9 @@ To fetch an identity assertion given a {{USVString}} -dictionary IdentityProviderToken { - required USVString token; +dictionary IdentityAssertionResponse { + USVString token; + USVString continue_on; }; @@ -1339,11 +1378,10 @@ an {{IdentityProviderAPIConfig}} |config|, an {{IdentityProviderRequestOptions}} :: The user's profile picture as given in {{IdentityProviderAccount}}.{{IdentityProviderAccount/picture}}. Any other string is ignored for forwards compatibility. - 1. The user agent MAY use the {{IdentityCredentialRequestOptions/context}} to customize the - dialog shown. + 1. The user agent MAY use the + {{IdentityCredentialRequestOptions/context}} and |options|'s + {{IdentityCredentialRequestOptions/mode}} 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|. 1. Return true. @@ -1507,6 +1545,45 @@ success or failure. 1. Otherwise, return failure. +
+To show a continuation dialog given a |continueOnUrl|, run the +following steps. This returns a failure or a tuple (string, string?) (a token +and an optional account ID). + 1. Assert: these steps are running [=in parallel=]. + 1. [=Create a fresh top-level traversable=] with |continueOnUrl|. + 1. The user agent MAY [=set up browsing context features=] or otherwise + affect the presentation of this traversable in an implementation-defined + way. + 1. Wait for the first occurence of one of the following conditions: + * The user closes the browsing context: return failure. + * {{IdentityProvider}}.{{IdentityProvider/close}} is called in the + context of this new traversable: + 1. Close the traversable. + 1. Return failure. + * {{IdentityProvider}}.{{IdentityProvider/resolve()}} is called in + the context of this new traversable. + 1. Close the traversable. + 1. Let |token| be the token that was passed to that resolve call. + 1. If {{IdentityResolveOptions/accountId}} was specified in the + resolve call, let |accountId| be that account ID. + 1. Otherwise, let |accountId| be null. + 1. Return (|token|, |accountId|). + +
+ +
+To fetch the config file and show an IDP login dialog given an +{{IdentityProviderConfig}} |provider|, and a |globalObject|, run the following +steps. This returns success or failure. + 1. Assert: these steps are running [=in parallel=]. + 1. Let |config| be the result of running [=fetch the config file=] + with |provider| and |globalObject|. + 1. If |config| is failure, return failure. + 1. [=Show an IDP login dialog=] with |config| and |provider|. + 1. If that algorithm succeeds, return success. + 1. Otherwise, return failure. +
+ ## The IdentityProvider Interface ## {#browser-api-identity-provider-interface} @@ -1522,8 +1599,13 @@ This specification introduces the {{IdentityUserInfo}} dictionary as well as the USVString picture; }; + dictionary IdentityResolveOptions { + USVString accountId; + }; + [Exposed=Window, SecureContext] interface IdentityProvider { static undefined close(); + static undefined resolve(DOMString token, optional IdentityResolveOptions options = {}); static Promise<sequence<IdentityUserInfo>> getUserInfo(IdentityProviderConfig config); }; @@ -1995,22 +2077,26 @@ the Origin header value is represented by the [=IDP=]-specific, the [=user agent=] cannot perform this check. -The response body must be a JSON object that can be [=converted to an IDL value|converted=] to an {{IdentityProviderToken}} without an exception. +The response body must be a JSON object that can be [=converted to an IDL value|converted=] to an {{IdentityAssertionResponse}} without an exception. -Every {{IdentityProviderToken}} is expected to have members with the following semantics: +Every {{IdentityAssertionResponse}} is expected to have members with the following semantics: -
+
: token :: The resulting token. + : continue_on + :: A URL that the user agent will open in a popup to finish the authentication process.
-The content of the {{IdentityProviderToken/token}} is opaque to the user agent and can contain +Only one of `token` and `continue_on` should be specified. + +The content of the {{IdentityAssertionResponse/token}} is opaque to the user agent and can contain anything that the [=IDP=] would like to pass to the [=RP=] to facilitate the login. For this reason the [=RP=] -is expected to be the party responsible for validating the {{IdentityProviderToken/token}} passed -along from the [=IDP=] using the appropriate token validation -algorithms defined. One example of how this might be done is defined -in [[OIDC-Connect-Core#IDTokenValidation]]. +is expected to be the party responsible for validating the +{{IdentityAssertionResponse/token}} passed along from the [=IDP=] using the +appropriate token validation algorithms defined. One example of how this might +be done is defined in [[OIDC-Connect-Core#IDTokenValidation]]. NOTE: For [=IDPs=], it is worth considering how [portable](https://github.com/fedidcg/FedCM/issues/314) accounts are.