Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support NoCredentialsError and UserCancelledError codes #2062

Open
AdamEisfeld opened this issue Apr 24, 2024 · 7 comments
Open

Support NoCredentialsError and UserCancelledError codes #2062

AdamEisfeld opened this issue Apr 24, 2024 · 7 comments
Assignees

Comments

@AdamEisfeld
Copy link

AdamEisfeld commented Apr 24, 2024

Related

An alternate solution to #1568 / the issues described in #1749.

To Sum Up

The current paradigm creates a bad UX, because we have no way of knowing if a user has connected their current device to web auth, and so we either need to show them UI for both Create and Get paths, relying on their memory of what they have done, or we need to conditionally only show them the Get path if we "think" the user has connected their current device (based on some information passed to us from our backend), potentially causing the user to attempt web auth and fail if their connection has been destroyed for some reason.

Currently there are 2 existing proposed solutions to this problem out there:

Proposed Solution A is to implement a "Get OR Create" mechanism that handles the details under the hood. This would solve the primary issue but introduces other concerns around state / the desire for frontends to provide a bit of a custom Create path (eg; entering a custom "device name" to associate with the key).

Proposed Solution B is to implement a "does key exist" function that returns a simple boolean depending on whether or not the current device is indeed found in the list of allowed credentials. This would solve the primary issue but introduces possible anonymity / privacy concerns.

I would like to offer a third proposal.

Proposed Change

Currently, if a user attempts to Get credentials for a device that does not have any, they see some form of automatic "Passkey does not exist" UI, requiring the user to click a Cancel button to back out of the procedure. The error thrown from clicking this Cancel button is a DOMException, with a name of "NotAllowedError". This is the same error that is thrown if a user DOES have credentials for their device but still opts to manually cancel out of the procedure.

Change 1

Throw "NoCredentialsError" if the user cancels out of a credentials.get() flow AND the reason for the cancellation is due to the device not having any credentials.

Change 2

Throw "UserCancelledError" if the user cancels out of a credentials.get() flow AND the reason for the cancellation is NOT due to the device not having any credentials.

Benefits

  • NotAllowedError can be used to inform the user that something is actually wrong.
  • We can detect UserCancelledError and just silently back the user out of the process, instead of worrying about whether or not we need to alert them to some issue. They chose to cancel, we don't need to explain it to them.
  • We can detect NoCredentialsError and store a cookie on the device in order to avoid showing the Get flow and instead show the Create flow on next-launch, until the user goes through with the creation. Meaning if they close the website entirely and come back, they won't need to go through this unhappy path again.
  • We can automatically push the user through to the Create flow as a result of this error, now that we know they don't have any credentials on this device.
  • The Get / Create flows remain separate instead of a possibly over-controlling "Get OR Create" flow that would make it more difficult for custom state-based interactions to take place (eg; entering a custom device name in the case of Create to be passed up to your backend)
  • Although this is similar to Solution B ("does key exist") in the sense it exposes some form of anonymous information, the information is obtained as a result of the user attempting to get them, instead of hidden from the user as a preliminary background step.
  • I imagine the required code change would be minimal as these are just error codes, perhaps with a bit of state tracking to determine which to throw

Risks

  • This could affect backwards compatibility, assuming people are already looking for the NoCredentialsError explicitly.
@timcappalli
Copy link
Member

timcappalli commented Apr 24, 2024

The current paradigm creates a bad UX, because we have no way of knowing if a user has connected their current device to web auth, and so we either need to show them UI for both Create and Get paths, relying on their memory of what they have done, or we need to conditionally only show them the Get path if we "think" the user has connected their current device (based on some information passed to us from our backend), potentially causing the user to attempt web auth and fail if their connection has been destroyed for some reason.

@AdamEisfeld this is what the autofill UI is designed for. If the user has a passkey on the device, it will appear in the autofill UI and user testing has shown that most users click it. If the user doesn't have a passkey on the device, they type in the email or username, and then you can initiate a creation flow, using the value from the form.

@AdamEisfeld
Copy link
Author

@timcappalli That works if we just want a single mechanism of authentication, but (for example) in the current project I am building we require multi-factor authentication on sign-in, meaning the user must enter their email / password first, and then they must provide some other mechanism of authentication (this is where we allow the user to either use an OTP or - preferably - the credentials system.

In this case, the user isn't entering a password when it comes time to access the credentials. I could have certainly missed something but am I correct in my understanding that the Autofill UI doesn't help with this scenario?

@timcappalli
Copy link
Member

@AdamEisfeld passkeys are not intended to be used as secondary credentials / second factors. They are purpose built to replace traditional primary factors such as username/password and be the primary factor for user sign in (e.g. a single gesture, multifactor authenticator).

@AdamEisfeld
Copy link
Author

I can understand the reasoning as the goal seems to be to eliminate traditional email / passwords. That being said, the system works perfectly well as a secondary credential in cases where added security is desired, barring this one poor user experience during the edge case of the user having registered their device previously and then removed it manually or attempting to sign in on a new device that isn't yet registered.

I'm sure many would love it to be resolved, but thats life I suppose.

@timcappalli
Copy link
Member

While it doesn't directly address your question/request, @MasterKale is working on improving some of the errors/exceptions in a way that maintains user privacy.

#2047

@MasterKale MasterKale self-assigned this May 1, 2024
@sbweeden
Copy link
Contributor

sbweeden commented May 1, 2024

Please keep in mind the concern that WebAuthn should not leak information about whther or not the user has credentials to the RP without user consent.

@MasterKale
Copy link
Contributor

Please keep in mind the concern that WebAuthn should not leak information about whther or not the user has credentials to the RP without user consent.

Good call out @sbweeden, this has been top of mind for me since I started pondering improved error messaging. Previous conversations over the years have made it clear that any changes to error messaging without a user interaction before it is verboten. @AdamEisfeld's proposed changes require user interaction, and the additional error messages I'm proposing below do as well.

Earlier this week I met with some colleagues internally and brainstormed some new error messages that would be nice to have as an RP that wants more visibility into why users are having issues signing in, and to potentially offer inline remediation advice:

NoCredentialsError and UserCancelledError

We independently came up with the same two NoCredentialsError and UserCancelledError errors that @AdamEisfeld has already proposed, so I'm in support of these.

For NoCredentialsError, we agree that if the user cancels out of the browser's "you have no credentials for this site" it necessarily means they tried to auth. That should count as consent, so why not let that propagate through to the RP?

For UserCancelledError, it would be great to pull this out of NotAllowedError as a new default error that gets returned after the user specifically cancels out of the modal experience. NotAllowedError is currently so overloaded it's difficult to understand whether the user experienced a legitimate issue with their browser + OS + authenticator, or simply decided to cancel out.

HybridPrerequisitesError

The thinking here is that there are prerequisites that must be met for the browser and/or platform to facilitate successful hybrid registration and auth. If these prerequisites aren't met, and the browser notifies the user of it...

Screenshot 2024-05-03 at 1 54 06 PM

Screenshot 2024-05-03 at 1 55 44 PM

Screenshot 2024-05-03 at 1 54 32 PM

...why not let this propagate through to the RP after the user cancels out? In response to this the RP could e.g. show custom UI to guide the user through setting everything up to try again.

UserHybridCancelError

Currently, browsers typically show the hybrid QR code to users when allowCredentials contains no locally usable credentials. An RP that could catch this kind of error after the user cancels out could better understand that the user cancelled out of the auth because they didn't know how to proceed, and guide the user accordingly.

Right now there's no way to understand this because an RP would receive a NotAllowedError in response to the user cancelling the modal experience.

UserVerificationError

Passkeys are typically paired with a requirement to perform UV, both during registration and auth. If the user clicks through the modal experience to create a passkey or try to auth, but has no authenticator available that can perform UV or can't enter the correct PIN or scan the correct biometric, can WebAuthn not let the RP know that that's why the user failed the ceremony? The browser definitely tells the user that this is the case...

Screenshot 2024-05-03 at 2 57 04 PM
U2F token in a userVerification:required registration ceremony

Screenshot 2024-05-03 at 2 56 04 PM
Security key PIN entry issue during auth ceremony

...so why not propagate this through to the RP?

I also pondered breaking this one up, into two separate errors:

  1. UserVerificationSupportError (e.g. U2F token when UV is required)
  2. UserVerificationCollectionError (e.g. User can't enter correct PIN or scan correct biometric)

...but maybe these are less useful/too complex for a single "UserVerificationError" 🤔

TimeoutError

If the user clicks to start a WebAuthn ceremony but gets distracted and walks away to the point that the ceremony times out, couldn't that signal be sent to the RP? It's not quite a true "error" that happened, which it can currently appear to be due to the currently ambiguous NotAllowedError, but would be helpful nonetheless in understanding that nothing actually went wrong during the ceremony.


Anyway as I mentioned earlier these were new errors that came out of a brainstorming session. I'd love to hear your thoughts, and welcome any suggestions from other RP's on other types of errors that we could try to incorporate into WebAuthn for sake of better understanding of the user's interaction with WebAuthn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants