Re: Benefits of exposing webauthn through navigator.credentials

> 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.

FWIW, I agree that #384 is the start of a longer conversation about
details, and I appreciate the high-level questions you've spelled out here.
Thanks for taking the time!

> - 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.

The credential management API's `requireUserMediation()` is a (perhaps
poorly-named) attempt to distinguish between a "signed-in" and a
"signed-out" user. For the latter, we need to ensure that the user is
always involved in the decision to hand over credentials. For the former,
the user agent might be able to hand over a password without asking the
user, if the user agent has gained permission to do so (via a "keep me
signed into this site" checkbox, for instance).

It seems to me that this is a pretty reasonable distinction to make at a
generic level, and that it's orthogonal to the question of whether a
specific type of credential imposes additional restrictions upon its usage.
That is, it seems reasonable to both support an RP that would accept "user
presence not required" assertion, but to interpose a prompt of some sort if
the user's signed out.

Personally, I think the real issue here is the method's name. Would you
still be concerned about the overlap it we renamed it to something like `
theUserTotallyJustSignedOutPleaseDontSignThemBackInWithoutAsking()`. That's
absolutely on the table, given the API's current implementation/deployment
(though perhaps with a little more thought put into the naming... :) ).

> - 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.

1. On the specific point of `cancel()`, I'd like to see y'all address that
using a platform-level cancellation mechanism along the lines of what we're
discussing in https://github.com/whatwg/dom/pull/434. That seems like the
right way to go, regardless of what happens with regard to credential
management.

2. I don't know what `promoteAuthenticatorIfAvailable()` would do (and it's
not coming up in a simple GitHub search), so I don't have much to say on
the specifics. In general, however, I agree that there's always a question
of whether it makes sense to extract type-specific behaviors up into a more
generic level. If "promotion" is something that's webauthn-authenticator
specific, then it would make sense to hang off of `ScopedCredential`. If
it's something that could apply to passwords, etc. as well, then it would
make sense to hang off of `CredentialsContainer`.

> - 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.

Skipping over `createAndStore()` for a moment (we'll get to it later):

The goal of `store()` is to deal with failure cases that arise with some
kinds of credentials. For example, if your password manager thinks that
your password is `hunter2`, but you've cleverly changed your password to
`hunter3`, we need an update mechanism. Likewise, if your password manager
has nothing stored for an origin, but you type `AzureDiamond`/`hunter2`
into a form, the page needs a mechanism of persisting that data once it's
verified.

I'd suggest that this latter case is pretty relevant to all credential
types. For example, assume for a moment that I have a security key on my
keychain that I registered on my work computer, and now wish to use at
home. The website knows I've registered, and calls `get()`. The user agent
doesn't know about my keychain key, so it asks the platform for anything. I
plug the key into the machine, use it to generate an assertion, and the
user agent hands it back to the website. Now the website has a
`ScopedCredential` which it can verify.

That credential might contain a valid assertion for the user. Great!
Perhaps we could use `store()` to teach the user agent about that
relationship so that it can do a better job with the UI next time.

It might also contain an invalid assertion (perhaps I have 15 security
keys, and I tapped the wrong one). Perhaps we wouldn't call `store()` in
this scenario, as we wouldn't want to persist the relationship between that
invalid key and this origin.

> - 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)

`ScopedCredential::create` is an analog to the constructor on
`PasswordCredential`. The only reason we don't have a constructor on
`ScopedCredential` is that JavaScript constructors are synchronous, and
`var s = new ScopedCredential(); s.register({ data });` seemed less elegant.

That said, I think there's a reasonable argument to be made that we should
move away from synchronous constructors for `PasswordCredential`, et al. as
well; giving the user agent room to intercede (by generating passwords, for
instance) seems like a totally reasonable thing to do.

If we allow the user agent to intercede, then I think it's pretty
reasonable to take the user interaction with that intercession as
permission to persist the data.

I also agree with your assertion that there's more arguing to do regarding
`createAndStore`, but I don't think that's actually a blocker. Aligning all
the credential types with an asynchronous constructor
(`navigator.credentials.create()`) gives us the freedom to experiment with
creation behaviors in the future if we decide to go that route.

> 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.

I (shockingly, I know!) disagree! I think it holds together pretty well
from the perspective of a developer who just wants a thing they can use to
verify that a given session is associated with a given user. There's a
great deal of value in reducing the overhead for folks who just want to get
on with their day. "Use this API for auth." seems like a good thing to tell
them.

If the above is a reasonably complete list of the ways in which you think
these credential types diverge, then I'd suggest that you don't really even
need to squint. We just need to do the work of sanding down the edges and
filling in the gaps.

> 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.

1.  `SiteBoundCredential` is gone from the current CM draft.

2.  I agree that `ScopedCredential` is a strange name. I like Jeffrey's
suggestion of `PublicKeyCredential`, but I'll happily leave painting that
bikeshed up to y'all. :)

3.  Rooting `WhateverCredential` in `Credential` seems like a good first
step. I think the IDL in #384 is a reasonable approach at doing so, but I'm
sure we could find a more minimal patch that just changed some type
information if this is all we end up agreeing on.

> 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.

The claim that webauthn is typically done "after the user signed in" is
probably true for the yubico devices I have in my pocket today, which are
clearly valuable as second-factors, but risky as first-/only-factors. I'm
not sure it's true for cases where a more capable authenticator is present.
My understanding of our conversations in the past is that there is a desire
for this API to support authentication styles that encompass those use
cases that might reasonably be used as authentication in and of themselves.
If that's the case, then asking for a password _or_ an assertion as the
first step of a sign-in flow makes a good deal of sense, especially as a
site transitions from one to the other. While it's possible to ask for them
in serial, it would be nice to teach the user agent to work with the user
to choose a reasonable authentication strategy in one step.

I'd also note that Jeffrey's `createAndStore` is a little different than
what I'm suggesting in `navigator.credentials.create()` above: the latter
is merely an asynchronous replacement for the existing constructors. We
could feasibly build on that to allow the user agent to help the user agent
fill in incomplete data, but I don't think that such capabilities are a
blocker for the integration we're discussing, as discussed above.

Thanks again for your time. :)

-mike

Received on Monday, 10 April 2017 11:04:46 UTC