diff --git a/specs/credentialmanagement/index.src.html b/specs/credentialmanagement/index.src.html index b3bfb2be..56a8e34c 100644 --- a/specs/credentialmanagement/index.src.html +++ b/specs/credentialmanagement/index.src.html @@ -238,7 +238,7 @@
navigator.credentials.get({ - "types": [ "password" ] + "filters": { "password" : {} } }).then( function(credential) { if (!credential) { @@ -248,7 +248,7 @@@@ -432,7 +435,8 @@Password-based Sign-in
// really be progressive enhancement on top of an existing form). return; } - if (credential.type == "PasswordCredential") { + if (credential.type == "password") { credential.send("https://example.com/loginEndpoint") .then(function (response) { // Notify the user that signin succeeded! Do amazing, signed-in things! @@ -269,16 +269,17 @@Federated Sign-in
navigator.credentials.get({ - "types": [ "federated" ], - "options": { - "providers": [ "https://federation.com" ] + "filters": { + "federated": { + "providers" : [ "https://federation.com" , "https://accounts.google.com/" ] + } } }).then( function(credential) { if (!credential) return; - if (credential.type == "FederatedCredential") { + if (credential.type == "federated") { switch (credential.provider) { case "https://www.facebook.com/": // Use Facebook's SDK, a la https://developers.facebook.com/docs/facebook-login/login-flow-for-web/#logindialog @@ -306,9 +307,10 @@Note: This API does not go out to the identity provider to grab a token, - or authenticate the user in any way. It provides a hint to the website as - to which identity provider the user prefers to use, and little more. See - [[#future-work]] for directions future versions of this API could take. + or authenticate the user in any way against a federated identity provider. + It provides a hint to the website as to which identity provider the user + prefers to use, and little more. See [[#future-work]] for directions future + versions of this API could take.Federated Sign-in
Post-sign-in Confirmation
@@ -329,6 +331,7 @@Post-sign-in Confirmation
credential.send("https://example.com/sign-in/") .then(function (response) { if (response.status == 200) + // Inspect response to determine if this was a successful login then... navigator.credentials.store(credential); });
Credential
Credential
password
, and {{FederatedCredential}} objects have a
{{[[type]]}} of federated
.
isMatch()
isMatch()
function. This hook allows different {{Credential}}
+ types to define how they will match the values from the {{CredentialRequestFilters}}
+ passed into the {{CredentialContainer/get()}} function. The base class implementation returns
+ true
.
+ Credential
Match
if the object matches an
- {{CredentialRequest/options}} dictionary, and Does Not Match
- otherwise.
+ All {{LocallyStoredCredential}} objects MUST define a filter matching
+ algorithm in the body of the {{Credential/isMatch()}} function.
+ The algorithm must return true
if there is a Match otherwise false
+ if the {{Credential}} Does Not Match.
PasswordCredential
@@ -616,8 +640,8 @@Matching Algorithm
- {{PasswordCredential}} objects' options matching algorithm always - returnsMatch
. + {{PasswordCredential}} objects' filter matching algorithm always + returnstrue
(Match).
FederatedCredential
@@ -626,10 +650,16 @@@@ -645,6 +675,11 @@
FederatedCredenti DOMString? protocol; }; + dictionary FederatedCredentialFilter { + sequence<USVString> providers; + sequence<DOMString> protocols; + }; + [Constructor(FederatedCredentialData data), Exposed=Window] interface FederatedCredential { readonly attribute USVString provider; readonly attribute DOMString? protocol; + boolean isMatch ( FederatedCredentialFilter filter ); }; FederatedCredential implements LocallyStoredCredential;
FederatedCredenti "openidconnect"). If this value is
null
, then the protocol can be inferred from the {{FederatedCredential/provider}}. +isMatch() ++ All {{FederatedCredential}} objects expect a {{FederatedCredentialFilter}} as the argument + passed to their {{Credential/isMatch()}} function. + {{[[type]]}} All {{FederatedCredential}} objects have their {{[[type]]}} @@ -656,23 +691,23 @@ Matching Algorithm
- {{FederatedCredential}} objects' options matching algorithm is as + {{FederatedCredential}} objects' filter matching algorithm is as follows. Given a {{FederatedCredential}} (credential) and a - {{CredentialRequest/options}} (options): + {{FederatedCredentialFilter}} (filter):
- - If options has a - {{FederatedCredentialRequestOptions/providers}} property: + If filter has a + {{FederatedCredentialFilter/providers}} property:
- - For each provider in options' + For each provider in filter's
providers
list:
- - Return
@@ -681,17 +716,17 @@Matches
if credential's + Returntrue
if credential's {{FederatedCredential/provider}} is a case-sensitive match for provider.
- - If options has a - {{FederatedCredentialRequestOptions/protocols}} property: + If filter has a + {{FederatedCredentialFilter/protocols}} property:
@@ -769,39 +804,50 @@
- - For each protocol in options' - {{FederatedCredentialRequestOptions/protocols}} list: + For each protocol in filter's + {{FederatedCredentialFilter/protocols}} list:
- - Return
@@ -700,7 +735,7 @@Matches
if credential's + Returntrue
if credential's {{FederatedCredential/protocol}} is a case-insensitive match for protocol.
- - Return
Does Not Match
. + Returnfalse
(Does Not Match
).{{CredentialContainer/get()}}, the caller specifies a few parameters in a {{CredentialRequest}} dictionary. - Note: The {{CredentialRequestOptions}} typedef is an extension point. If and - when new types of credentials are introduced that require options, their - dictionary types will be added to the typedef so they can be passed into the - request. -
- dictionary FederatedCredentialRequestOptions { - sequence<USVString> providers; - sequence<DOMString> protocols; + interface CredentialFilterMap { + readonly attribute unsigned long size; + + getter any get(DOMString credentialType); + setter creator void set(DOMString credentialType, any value); + deleter void remove(DOMString type); }; - typedef FederatedCredentialRequestOptions CredentialRequestOptions; + typedef CredentialFilterMap CredentialRequestFilters; dictionary CredentialRequest { - CredentialRequestOptions options; + CredentialRequestFilters filters; boolean suppressUI = false; - sequence<DOMString> types; }; +--
- providers
-- - An array of federation identifiers. For details regarding valid formats - see [[#identifying-federations]]. -
-
- types
+- filters
- - A sequence of strings specifying the types of {{Credential}} objects which - the caller wishes to obtain. An empty sequence signifies that the caller - will accept any credential type. For processing details, see the algorithm - defined in [[#request-credential]]. + A map containing a set of parameters which will be used to select + a particular {{Credential}} to return. +
+- + Each key in the map should correspond to the value of the + {{Credential/type}} property of any {{Credential}} type that + should be returned. +
+- + For each possible credential that can be returned: +
++
+ Built-in {{Credential}} types define an filter matching algorithm for that credential type. +- If the {{Credential/type}} is not a defined key then the {{Credential}} + is immediatley excluded.
+- If the {{Credential/type}} is a defined key then let V be the value in + the map stored under that key. Call {{Credential/isMatch()}} passing V + as the only argument. If the the function returns
+false
then exclude + the {{Credential}}.- + Custom {{Credential}} types can be defined in ECMAScript that override the default {{Credential/isMatch()}} + function and thereby provide custom matching logic.
- suppressUI
- @@ -812,12 +858,6 @@
-
undefined
. For processing details, see the algorithm defined in [[#request-credential]].- options
-- - An dictionary containing a set of parameters which will be used to select - a particular {{Credential}} to return. This process is described in each - {{Credential}} type's options matching algorithm. -
@@ -827,9 +867,10 @@
navigator.credentials.get({ - "types": [ "federated" ], - "options": { - "providers": [ "https://identity.example.com/" ] + "filters": { + "federated" : { + "providers": [ "https://identity.example.com/" ] + } } }).then(function (credential) { // ... @@ -843,9 +884,10 @@
navigator.credentials.get({ - "types": [ "federated" ], - "options": { - "providers": [ "https://identity.example.com/" ] + "filters": { + "federated" : { + "providers": [ "https://identity.example.com/" ] + } }, "suppressUI": true }).then(function (credential) { @@ -865,8 +907,8 @@Identifying providers
agents to be helpful. For consistency, federations passed into the APIs defined in this document - (e.g. {{FederatedCredentialRequestOptions}}'s - {{FederatedCredentialRequestOptions/providers}} array, or + (e.g. {{FederatedCredentialFilter}}'s + {{FederatedCredentialFilter/providers}} array, or {{FederatedCredential}}'s {{FederatedCredential/provider}} property) MUST be identified by the ASCII serialization of the origin the provider uses for sign in. That is, Facebook would be represented by @@ -932,18 +974,6 @@-
- - Let type be lowest common ancestor interface of the types - referenced in request's {{CredentialRequest/types}} property. - - ISSUE(w3c/webappsec#289): This is terribly hand-wavey. The intent is - clear, but I need to do the work to walk through the list of DOMStrings - and convert them to interfaces and etc. Busywork for later. -
-- - Return a
Promise
rejected withTypeMismatchError
- if type is {{Credential}}. -- Let promise be a newly created
@@ -960,9 +990,8 @@Promise
object.
- Let result be the result of executing [[#request-locallystoredcredential-without-mediation]], - passing in origin, request's - {{CredentialRequest/types}} property, and - request's {{CredentialRequest/options}} property. + passing in origin and + request's {{CredentialRequest/filters}} property.
- If result is not
null
, resolve @@ -985,9 +1014,8 @@
- Resolve promise with the result of executing [[#request-locallystoredcredential-with-mediation]], - passing in origin, request' - {{CredentialRequest/types}} property, and - request' {{CredentialRequest/options}} property. + passing in origin and + request' {{CredentialRequest/filters}} property.
@@ -1004,8 +1032,8 @@{{LocallyStoredCredential}} isn't the only type? For example, how could an author pull some "RemoteCredential" that required interaction with a third-party, but fall back to a {{PasswordCredential}}? For the moment, we're - resolving this by rejecting the promise if the types listed in - {{CredentialRequest/types}} aren't all instances of the same type, but that + resolving this by rejecting the promise if the types listed as keys in + {{CredentialRequestFilters}} map aren't all instances of the same type, but that seems like something we'd want to fix.
@@ -1083,9 +1111,8 @@
Gather
- Given an origin (origin), and a sequence of {{DOMString}} - (types), and an {{CredentialRequest/options}} dictionary - (options), this algorithm returns a sequence of + Given an origin (origin) and {{CredentialRequest/filters}} map + (filters), this algorithm returns a sequence of {{LocallyStoredCredential}} from the user agent's credential store which are potential candidates: @@ -1103,13 +1130,26 @@LocallyStoredCredential
sbased on vendor-specific heuristics (as described in [[#synthesis]]).
- - Remove any items from credentials whose {{Credential/type}} - property is not a case-sensitive match for a value in types. -
-- - Remove any items from credentials whose options matching - algorithm returns
Does Not Match
when executed on - options. + For each {{Credential}} in credentials, let type be + the value of the {{Credential/type}} property of the {{Credential}}. ++
- Let filter be the value returned by calling {{CredentialFilterMap/get()}} + and passing type as the argument
+credentialType
. ++
+- + If filter is undefined remove the {{Credential}} from + credentials and process the next {{Credential}}. +
+- + Call {{Credential/isMatch()}} passing filter as the only parameter. +
+- + If the function returns
+false
remove the {{Credential}} from + credentials and process the next {{Credential}}. +- Return credentials. @@ -1120,9 +1160,8 @@
Request a
- This algorithm accepts an origin (origin), a sequence of - type names (types) and an {{CredentialRequest/options}} dictionary - (options), and returns either a single {{LocallyStoredCredential}} + This algorithm accepts an origin (origin) and {{CredentialRequest/filters}} + map (filters), and returns either a single {{LocallyStoredCredential}} object if and only if one can be provided without user mediation, orLocallyStoredCredential
without user mediationnull
if not. @@ -1137,8 +1176,8 @@
- Let credentials be the result of executing - [[#gather-locallystoredcredentials]] on origin, - types, and options. + [[#gather-locallystoredcredentials]] on origin + and filters.
- If credentials is empty, or contains more than one @@ -1165,16 +1204,15 @@
Request a
- This algorithm accepts an origin (origin), a sequence of - type names (types) and an {{CredentialRequest/options}} dictionary - (options), and returns either a single {{LocallyStoredCredential}} + This algorithm accepts an origin (origin) and {{CredentialRequest/filters}} + map (filters), and returns either a single {{LocallyStoredCredential}} object, orLocallyStoredCredential
with user mediationnull
if none can be provided.
- Let credentials be the result of executing - [[#gather-locallystoredcredentials]] on origin, - types, and options. + [[#gather-locallystoredcredentials]] on origin + and filters.
This requirement only applies if we would show UI to the user. If she's elected to allow credentials to be provided without user @@ -1491,7 +1529,7 @@Requiring User Mediation
{{Credential}} objects from that origin MAY be provided to pages from that origin without user interaction. The user will be signed-in to that origin persistently, which, on the one hand, is desirable from the perspective of - usability and convinience, but which might nevertheless surprise the user. + usability and convenience, but which might nevertheless surprise the user. If the user agent syncs the state of a {{Credential}} between devices, an origin could explicitly tie the devices together in a way which might surprise @@ -1578,8 +1616,8 @@Synthesizing Credentials
- Switch on the interface whose {{Credential/type}} property matches one of - the items in the {{CredentialRequest}} {{CredentialRequest/types}} - sequence, and execute the associated steps: + the keys in the {{CredentialRequest}}'s {{CredentialRequest/filters}} + map, and execute the associated steps:
- {{FederatedCredential}}
@@ -1590,7 +1628,7 @@Synthesizing Credentials
- - Let providers be the value of options' + Let providers be the value of filter's providers property.
- @@ -1599,8 +1637,8 @@
Synthesizing Credentials
- - For each provider in options' - {{FederatedCredentialRequestOptions/providers}} property: + For each provider in filters' + {{FederatedCredentialFilter/providers}} property:
- @@ -1649,7 +1687,7 @@
Synthesizing Credentials
being a bit counterproductive, as the site needs to display a list of dozens of providers, hoping that users will see an icon they remember. - A call to {{get()}} will end up with an empty list of {{Credential}} objects + A call to {{CredentialContainer/get()}} will end up with an empty list of {{Credential}} objects for new users, as they won't have anyhttps://example.com/
credentials. They might, however, have a {{PasswordCredential}} stored for the exciting social media sitehttps://federation.com/
, which @@ -1662,9 +1700,9 @@Synthesizing Credentials
it to the user as a sign-up options in a chooser. In this way,https://example.com/
can support dozens of - providers as arguments to {{get()}} (via - {{FederatedCredentialRequestOptions}}'s - {{FederatedCredentialRequestOptions/providers}} property), and the user + providers as arguments to {{CredentialContainer/get()}} (via + {{FederatedCredentialFilter}}'s + {{FederatedCredentialFilter/providers}} property), and the user agent can cull that list down to the federations with which the user regularly interacts (or which have been previously used as {{FederatedCredential}}s, or any of a number of other heuristics). @@ -1801,16 +1839,16 @@Privacy Considerations
Timing Attacks
- If the user has no credentials for an origin, a call to {{get()}} will + If the user has no credentials for an origin, a call to {{CredentialContainer/get()}} will resolve very quickly indeed. A malicious website could distinguish between a user with no credentials and a user with credentials who chooses not to share them. This could allow a malicious website to determine if a user has credentials saved for particular federated identity providers by repeatedly calling - {{get()}} with a single item in the - {{FederatedCredentialRequestOptions/providers}} array. The risk is mitigated - by the fact that a user-mediated {{get()}} is tied to a user gesture, and the + {{CredentialContainer/get()}} with a single item in the + {{FederatedCredentialFilter/providers}} array. The risk is mitigated + by the fact that a user-mediated {{CredentialContainer/get()}} is tied to a user gesture, and the user would, sooner or later, be prompted to provide credentials to the site, which would certainly raise her suspicions as to its behavior. @@ -1875,19 +1913,23 @@Extension Points
interface ExampleCredential : Credential { // Definition goes here. + };- - Define the options that the new credential type requires, and add them - to the {{CredentialRequestOptions}} typedef: + Define a strongly typed filter for the new credential type, and override the isMatch() + function:
- dictionary ExampleCredentialRequestOptions { + dictionary ExampleCredentialFilter { // Definition goes here. }; - typedef (FederatedCredentialRequestOptions or ExampleCredentialRequestOptions) CredentialRequestOptions; + partial interface ExampleCredential { + boolean isMatch( ExampleCredentialFilter filter); + }; +