w3c / webauthn

Web Authentication: An API for accessing Public Key Credentials
https://w3c.github.io/webauthn/
Other
1.17k stars 169 forks source link

How to know if a user has already registered a device? #1749

Closed dagnelies closed 2 years ago

dagnelies commented 2 years ago

So, here is the scenario...

The user "john.doe@example.com" opens his browser on some website and user John Doe of course has multiple devices like a laptop, a tablet, a phone, and so on.

Ideallly, when opening the authentication screen, some guidance is helpful. Mainly requesting the user to login with its existing credentials with credentials.get or to register a new device with credentials.create.

So basically... how to check if the user has already registered its device? Is this possible?

Any "local" solution like cookies, localStorage and so on are of course problematic because it can be cleared, the user might switch browser, be incognito, and so on. It is also fairly straightforward to get the list of registered credential ids from the server, but how can you check if any of them is present on the device?

agl commented 2 years ago

A website can't silently check whether the browser already has credentials without triggering the (interactive) sign-in experience (for obvious privacy reasons). It can, however:

dagnelies commented 2 years ago

Hmmm... That's a pitty.

A website can't silently check whether the browser already has credentials without triggering the (interactive) sign-in experience (for obvious privacy reasons).

What would be the privacy issues of a credentials.exists(credentialId)? It might be obvious for you but I don't get it.

  • Ask the browser to show any known credentials in autofill when the user selects a username field, for cases where both passwords and WebAuthn are supported. (Browser support for this is still in development though.)

Well, autofills are notoriously unreliable/inconsistent.

  • Know when the user just signed-in using a non-local device, like a security key or phone, which might be a good signal to try registering the local device.

I wonder if there is confusion about this topic. The goal is to know if credentials should be created for the user or already exist. After all, it's alien for users. They are used to register their account once, not registering their devices individually. With webauthn, instead of register/login, you should have three options:

Since users are unfamiliar with the concept of registering all they devices separately, it would be a great help to provide guidance to the user by highlighting either "Sign in" or "Register device" if they are on another device... Or "Create account" if they haven't a user account yet.

dagnelies commented 2 years ago

Otherwise, when using another device, the user might intuitively click on "Login" without understanding why it does not work.

Just to be clear. I'm talking from an RP point of view, mapping a single user account to multiple credentials, one per user's device. However, there is currently no way to distinguish if you are on a device which already has the credential or a new unknown device requiring new credentials.

emlun commented 2 years ago

What would be the privacy issues of a credentials.exists(credentialId)? It might be obvious for you but I don't get it.

The first issue is that it could be used as a "super-cookie" to track users without explicit consent. Then add to that that it won't even work for roaming authenticators (e.g., external security keys) since they're unlikely to be plugged in all the time.

We're aware that this is a difficulty for RPs, but we won't add a plain credentials.exists(credentialId) for these reasons. I think #1576 and/or #1568 are more likely candidates for solving this problem.

timcappalli commented 2 years ago

Please consider joining the WebAuthn Community Adoption Group!

dagnelies commented 2 years ago

I think https://github.com/w3c/webauthn/pull/1576 and/or https://github.com/w3c/webauthn/issues/1568 are more likely candidates for solving this problem.

Indeed a "get-or-create" call like suggested in #1568 would do the trick. However, how it exactly would work (since both operations have very different input/ouput objects) sounds rather tricky.

  • Most platform credentials are moving to a multi-device model over the next 12-18 months, meaning the credential is available on all of a user's devices without re-enrollment

But isn't that a "same platform" thingy. Like if I have an IPhone and a Windows Desktop, it won't sync credentials anyway.

  • The new conditional UI for WebAuthn (e.g. autofill) is quite reliable as it is platform integrated. You can test it out in Chrome on Mac and Safari (iOS 16, macOS 13).

Is this the "pick a user" dialog when you make a get without credential IDs? Or is it something else?

Please consider joining the WebAuthn Community Adoption Group!

Thanks for the invitation, I'll think about it, I'm just a bit short on time right now.

timcappalli commented 2 years ago

But isn't that a "same platform" thingy. Like if I have an IPhone and a Windows Desktop, it won't sync credentials anyway.

Yes, credentials are backed up across devices in the same ecosystem, but can also be used to authenticate across ecosystems. For example, you can use a passkey on your iPhone to sign into a site on Windows. The credential does not move, but an assertion is made across devices. In this cross-device flow, it is recommended that the RP then initiate a makeCredential to create an additional passkey in that ecosystem (Microsoft in that example). This way the user can use the local platform for subsequent authentications instead of having to pull their phone out.

Is this the "pick a user" dialog when you make a get without credential IDs? Or is it something else?

Correct

dagnelies commented 2 years ago

I'm really curious how people work around this problem, because I still haven't found a solution.

Using a fictive dialog like this is a disaster:

image

In this example, the "Register" would be the equivalent of a credentials.create and "Sign In" of credentials.get.

If the user "registered" on its windows laptop, and then opens the page with its android phone, clicking "Sign In" would be the most intuitive choice for almost every user. Even if you rename it more accurately "Register device", many users would find it disturbing. Moreover, clicking on "Sign In" would not fail, it would prompt for external security keys since a local one is not present which would confuse the user even more.

So, how do others tackle this?

ndpar commented 2 years ago

@timcappalli The conditional UI will only work for discoverable credentials, won't it?

timcappalli commented 2 years ago

@timcappalli The conditional UI will only work for discoverable credentials, won't it?

Correct. The workflow / use case to support passkeys is only possible with discoverable credentials.

timcappalli commented 2 years ago

Please continue this discussion in the WebAuthn Community Adoption Group.

dagnelies commented 1 year ago

Well, it's now several months later, and I still face the same issue. Just being able to highlight or disable the "Sign In" button would be a huge help, and for that, you need to know if the device has credentials.

If credentials.exists(credentialId) is a privacy issue, then even a simpler credentials.exist() without indication would be tremendously helpful for user guidance. While not perfect, it would help in most cases to highlight the right button(s) or offer appropriate UI hints.

timcappalli commented 1 year ago

The autofill UI (Conditional UI) with passkeys are the solution here for the current specifications and implementations.

Firstyear commented 1 year ago

And when tim says "passkeys" they mean "credentials that were platform bound and opportunistically created resident/discoverable keys" or "FIDO2 devices where rk=true was requested at registration".

The trick here is most platform authenticators will create a resident/discoverable key even if the registration requests rk=false. That allows you to mostly discover them.

However with fido2 devices (such as yubikeys) you can't rely on this because they will not create rk unless you create it. You also should not request rk=true outside of some circumstances with these devices because they have finite storage, some keys as low as 8 rks are available. This means "if everyone set rk=true, some users will only be able to login to 8 websites maximum".

So remember, you can use conditional UI to opportunistically find out if the user has an rk, but you still need to account for cases where they do not, and they need to enter a username and proceed that way.

andreujuanc commented 1 year ago

I agree with @dagnelies here. Almost all docs/guides/tutorials/articles include a register and login button, which makes no sense. This "bad example" will be copied pasted in new apps using webauthn.

Moreover, when proceeding with a login (get) without prior registration the user is prompted into a nonsense UI on windows, and a really horrible "no passkeys for " on android.

IMHO, this should have been a single function to create a proof/signature of the device, without any registration/login difference.

Current implementation will reduce or at least slowdown adoption.

I understand that this was developed by very smart people, but definitely not someone with UX in mind.

Jorgu5 commented 1 year ago

+1

Knowing whether a user has already registered a device with this Relying Party (RP) could be highly beneficial. With this information, we could implement tailored flows—such as distinct pathways for "legacy" users relying on passwords and for users who have already set up passkeys.

mitar commented 10 months ago

I do not understand why credentials.exists(credentialId) is a super-cookie? Because it really is credentials.exists(rpId, credentialId). So credentialId makes sense only for ipId where only ipId which matches the site origin can use. So you can only check for credentialId for your site. In fact, I think only credentials.exists(rpId) is all that is needed. Is there any credential stored for the RP? How is that a super-cookie?

Firstyear commented 10 months ago

I think only credentials.exists(rpId) is all that is needed. Is there any credential stored for the RP? How is that a super-cookie?

Because if you are doing the responsible thing, and not forcing discoverable credentials, you need the credentialIds present to pass to devices to check that they match for this rpId or not.

mitar commented 10 months ago

@Firstyear I do not think this has much with privacy concerns of super-cookies. And I can request both "platform" and "discoverable credentials" combo and to my knowledge no platform has issues storing many discoverable credentials. Only "cross-platform" do.

agl commented 10 months ago

I think only credentials.exists(rpId) is all that is needed. Is there any credential stored for the RP? How is that a super-cookie?

Firstly, browsers don't have perfect information to answer with: perhaps the user has a credential on a security key or phone that they would like to use.

Also, RP IDs are not a limited resource. A site can create many subdomains and register different users on different subdomains, then probe for credentials on each. So the fingerprinting surface is more than one bit.

The combination is enough to make things problematic. There have been requests for a "conditional modal" UI, i.e. a modal UI that appears only if a credential is known to exist. That still has the first problem, suggesting that something should always appear for users who really want to use WebAuthn (perhaps in the URL bar), but would be a middle ground between modal and autofill UIs. There is not currently enough energy behind this idea to make it real, however.

mitar commented 10 months ago

A site can create many subdomains and register different users on different subdomains, then probe for credentials on each. So the fingerprinting surface is more than one bit.

This is the same as creating bunch of 1st party cookies to do the same? I understand super-cookie as something which works across origins and allows an attacker to trace you across sites. If super-cookie means "almost no expiration 1st party tracing cookie" then yes, exists would enable that. But it would not enable cross-site tracing on its own. Different origins would have to coordinate between them. Like they have to do when tracing with 1st party cookies today.

Personally, I think what is needed is just getOrCreate API call and this is it. This is all I need. If user is already registered with the site, return its credential, if not, register. As a site I do not really care. I just want a credential I can use. User should also not care or remember if they have to "sign in" or "sign up" on the particular site. Just press "use keypass" button and you are good.

It seems #1568 is already asking for this.

Firstyear commented 10 months ago

And how does your "sign in or sign up" that just trusts everything, plan to verify the public key that signed the challenge?

I think you need to step back, re-read the specification, and understand the various types of authenticators and how webauthn works.

mitar commented 10 months ago

And how does your "sign in or sign up" that just trusts everything, plan to verify the public key that signed the challenge?

Trust on first use. I would not care about what user uses as an authenticator. Once site obtains the credential it creates the account and remembers the credential (public key) and requires that assertion is signed by the same credential (public key) in the future for somebody to sign-in into that account.

Firstyear commented 10 months ago

There are a lot of holes in this plan. I also think it's not worth my time to explain them - you have decided that "you want things to work a certain way" and are trying to force webauthn to do things that it never will.

If you plan to use webauthn, then you need to step back, re-read the specification, understand why decisions have been made as they have and the risks that they protect from, and then use it correctly.

Is webauthn perfect? No. Is it better than alternatives? Absolutely.

dagnelies commented 10 months ago

Whatever the use case and considerations, having a method:

credentials.exists(rpId)

is useful to adapt the UX accordingly. In the simplest case, just to highlight a button "Sign in with your passkey" while suggesting two other buttons "Sign up" or "Use roaming device" in the other case. It does not carry any personal identifying information either, so it's safe.


@agl If I understand right, the potential tracking risk you mention goes as follow:

But that logic is flawed since you need to redirect the unknown user to the right subdomain in the first place. In other words, you need to identify it before making the redirect.

While we talk about privacy, the concrete situation we are in is that "discoverable" credentials are pushed. All such credentials are synced with Google/Microsoft/Apple having de facto a copy of your account keys. Greetings to NSA by the way.

That's very ironic to say the least.


Regarding the getOrCreate, I agree with @mitar that getOrCreate would be an alternative solution for the default use case. Since you cannot know if the user has an existing credential or not, just let the user pick an existing one or create one on demand... But UX related still sub-par compared to exists which can guide the user and customize UI accordingly.

agl commented 10 months ago

If I understand right, the potential tracking risk you mention goes as follow: user goes to weird-subdomain-just-for-him.tracker.xyz user registers with webAuthn each time user visits weird-subdomain-just-for-him.tracker.xyz, that person can be tracked

WebAuthn calls can be made on subdomains within iframes, which would be a lot more effective. The mobile APIs don't allow for an "exists" call either, and they are generally much more trusting given the much greater friction of installing a mobile app. So exposing that on the web seems unlikely.

getOrCreate is possible if there's enough demand, thanks. It would be similar to the model of federated sign-in. I can't say that anything will happen quickly, but I'll keep it in mind along with a possible "conditional modal" mode.

While we talk about privacy, the concrete situation we are in is that "discoverable" credentials are pushed. All such credentials are synced with Google/Microsoft/Apple having de facto a copy of your account keys.

Google Password Manager e2e encrypts passkey secrets, Google cannot access them. Likewise for iCloud Keychain. If you don't trust them, 3rd parties like 1Password exist, or you can use a security key.

mitar commented 10 months ago

getOrCreate is possible if there's enough demand, thanks.

Thanks. I would also note this SO question which is asking for the same thing. And there is already #1568 for it as well.

dagnelies commented 10 months ago

Well, even an exists() checking existence of a public key credential:

would be really simple, without tracking info, and be helpful in 90% of the use cases to guide users.

I'm also surprised webauthn is supposed to work in iframes. IFrames are notoriously "unsafe by default" since you can eavesdrop and even manipulate anything that happens within. But that's another topic.

ragnarbull commented 10 months ago

getOrCreate is possible if there's enough demand, thanks. It would be similar to the model of federated sign-in.

@agl this would be great thank you

mitar commented 10 months ago

Let's trace getOrCreate in #1568. Subscribe/upvote that issue.

ragnarbull commented 10 months ago

@mitar I like your enthusiasm! But let's just check with the WebAuthn reps where getOrCreate is being tracked.

@agl @timcappalli is this being tracked in #1568 still?

dagnelies commented 10 months ago

While I agree that getOrCreate() is very convenient from a technical perspective, I would like to emphasize that exists() is far superior regarding simplicity and guiding the user through UI hints.

andreujuanc commented 10 months ago

exists() or similar would be superior due to its simplicity as stated by @dagnelies Without this, webAuthN is just a mere second factor.

Personally I don't dig 'getOrCreate()'. Seems that it might pollute local state.

hg0428 commented 3 months ago

My issue here is that the user may create a credential on a computer (e.g. Windows Desktop) and want to use it on mobile (e.g. iPhone). They may not be in the same ecosystem and so may not transfer. In addition, you can't use the first device to sign into the second. So, the only solution is to have a password backup and require the user to sign in with the password then create a credential.

Passkeys don't seem to be, in their current version, a real password replacement, but instead something to be used in addition.