Re: Benefits of exposing webauthn through navigator.credentials

Ok, I'll bite and argue the counter-point. :-)

To be sure, I'm not arguing against exposing webauthn though
navigator.credentials, I'm arguing against accepting
https://github.com/w3c/webauthn/pull/384 as is, because I believe it will
create a lot of future work for us that will slow us down.

Just one small example: you're highlighting in your message (which is a
"pitch to accept #384") the benefits of a createAndStore() method. But such
a method isn't part of #384. Presumably, we would have to do some work to
get from the state in #384 to having consensus around createAndStore().

There are quite a few open questions that accepting #384 would raise; I'll
list some below:

- It seems that requireUserMediation() is a no-op for ScopedCredentials.
But webauthn *does* have a notion of different kinds of tests of user
presence, they're just expressed differently in the API. In particular, we
distinguish between a simple test of user presence, and "user
verification". We're toying with the idea of introducing another "level" of
user-presence-test (user presence not required), but it's not clear whether
requireUserMediation() and its specific semantics are a good match for
that. For starters, in webauthn, whether or no a test-of-user presence is
required will likely be an attribute of an authenticator, not of an origin
(and there are other open questions). To summarize, how
requireUserMediation() and the various levels of tests-of-user-presence in
webauthn will collide or merge is not clear.

- We're thinking of introducing new methods to webauthn, such as a cancel()
method, and a promoteAuthenticatorIfAvailable() method. For each of those,
we'd have to think through what they mean for the other type of
credentials, and will have a problem if we realize that they don't apply
well to non-ScopedCredentials.

- Similar to requireUserMediation(), store() seems to be a no-op for
SopedCredentials. I think Jeffrey is trying to address this with this
proposal for a createAndStore() method, but the necessity to even come up,
and consider, and discuss, and argue over, and get consensus on, these
kinds of new proposals arises only if we accept #384 as is.

- Create() is a no-op for passwords. In #384, it's therefore a static
method on ScopedCredential. That seems a bit strange. Jeffrey seems to
agree, and therefore proposes a createAndStore() method. But that means
more arguing, etc. (see above)

To level this up a little: as y'all know, I agree with the goal of
de-confusing developers. I think we should make sure that the relative
positioning of these two APIs is clear. I appreciate that one possible way
to explain the relative positioning is to say "they're really the same
thing; if you squint you won't even notice the difference between these
types of credentials". But if #384 is our best attempt at taking that
position, I'm afraid it looks to me like that that story doesn't hold
together.

Another approach to de-confusing would be to say "these are different
things - they're related, but different in fundamental ways". We can tell
*that* story by re-naming things to stress the differences
(SiteBoundCredential and ScopedCredential is way too similar, for example),
by having a common root (both in inheritance and namespace), but by having
different sets of method that behave differently.

Which brings me to my last point - how important is it to have a single
get() method that can deal with passwords and Authenticators, and a single
createAndStore() method that also makes sense for passwords and
Authenticators? I don't want to argue that it's fundamentally a bad idea to
try for that unification, but I also don't think that it will really help
that much. Take Jeffrey's example for createAndStore() below. I don't
believe an RP would actually ever make such a call. Creating a publicKey
credential is typically something done *after* the user signed in, let
alone created an account, whereas creating a password credential is
presumably something that happens as the user is signing up for/creating an
account. So I would anticipate that those two calls
(createAndStore({password}) and createAndStore({publicKey})) would happen
in different contexts, on different pages, and that it wouldn't be the end
of the world if they looked slightly different. Similar arguments apply to
a unified get() - I believe that password users and Authenticator users
follow sufficiently different paths through a web site's UI that the
benefits of a unified get() methods aren't that huge.

Ok, I'll stop now - the post is already long enough. :-) But I do worry
that we add months to our timeline by incorporating #384.

Thanks,

Dirk.


On Wed, Apr 5, 2017 at 9:58 AM Jeffrey Yasskin <jyasskin@google.com> wrote:

I'm not going to be able to call in today, so I figured I'd send my pitch
to accept https://github.com/w3c/webauthn/pull/384 by email.

I think the biggest benefit of exposing webauthn credentials through the
same interface as passwords is that it lets us achieve Jeff Hodges' goal of
a single interface for webpages to use for sign-in, and with a small change
to #384 another single interface for credential creation.

For credential creation, the page needs to give the user a choice of which
kind of credential to create. Adding a couple more credential types, that
could look something like:

  navigator.credentials.createAndStore({
    accountInfo: {userid, username, userImage, siteName},
    acceptableCredentials: {
      password: {minEntropy: 64},
      federated: {providers:["https://accounts.google.com", "
https://www.facebook.com", ...]},
      sms: {
        checkBy: {
          send: function(number) {/*Ask the server to send an SMS*/},
          vouch: [googleKey, samsungKey, appleKey, ...],
        },
      },
      publicKey: { // <-- Better name for webauthn credentials.
        attestationChallenge: crypto.getRandomValues(sixteen_byte_buffer),
        cryptoParameters: [{algorithm: "ES256"}],
        // Do we need a filter for acceptable attestation certificates?
      },
    }
  });

The UI flow would be:
1) Get and validate a username, possibly taking advantage of
autocomplete="username" or autocomplete="email".
2) Call .createAndStore().
3) Potentially call .createAndStore() again to get a second factor.

You need a library to help manage the different credential types on the
server, but using a single call lets the browser guide the user through
their choice of credential types instead of needing to expose several "can
I use this" functions to the website.

Sign-in needs this unification a bit less, since most users have exactly
one credential for a given account, and in those cases, the site can
dispatch to the single call that works for that credential type. But some
users have multiple credentials for a given account, and in those cases,
letting the browser manage which credential to use would be useful.

  navigator.credentials.get({
    username,
    password: true,
    federated: {providers:["https://accounts.google.com", "
https://www.facebook.com", ...]},
    sms: {
        checkBy: {
          send: function(number) {/*Ask the server to send an SMS*/},
        },
      },
      publicKey: { // <-- Better name for webauthn credentials.
        challenge: crypto.getRandomValues(sixteen_byte_buffer),
        allowList: {id: id_for_username},
      },
    }
  });

Jeffrey

Received on Friday, 7 April 2017 21:11:04 UTC