RE: Benefits of exposing webauthn through navigator.credentials

Adding context for Mike: “promoteAuthenticatorIfAvailable()” refers to the (yet to be finally named) API proposed in https://github.com/w3c/webauthn/issues/345


From: Mike West [mailto:mkwst@google.com]
Sent: Monday, April 10, 2017 4:04 AM
To: Dominic Battre <battre@google.com>; Jeffrey Yasskin <jyasskin@google.com>; Hodges, Jeff <jeff.hodges@paypal.com>; Dirk Balfanz <balfanz@google.com>
Cc: public-webauthn@w3.org
Subject: 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<https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fw3c%2Fwebauthn%2Fpull%2F384&data=02%7C01%7Cvijaybh%40microsoft.com%7C519a9f64ef1344c0003908d4800181ea%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636274191350903532&sdata=kHEBeCq57AGmqQxE5vfypLMtQysKRv54qIGvGsr2cB4%3D&reserved=0> 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<https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fwhatwg%2Fdom%2Fpull%2F434&data=02%7C01%7Cvijaybh%40microsoft.com%7C519a9f64ef1344c0003908d4800181ea%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636274191350903532&sdata=Uz8Ua08j9bL0eA%2FpmfM8SIPyvRv0Ll9YX515CRBntwM%3D&reserved=0>. 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 15:38:52 UTC