passwordless-id / webauthn

Webauthn / passkeys helper library to make your life easier. Client side, server side and demo included.
https://webauthn.passwordless.id
MIT License
436 stars 51 forks source link

Add support for abort signal #52

Closed JohnnyCrazy closed 4 months ago

JohnnyCrazy commented 4 months ago

Hi,

Adds support for passing a AbortSignal via the optional signal property. This can be used to cancel credential requests, like when using mediation: 'conditional'.

A react hook example:

useEffect(() => {
  const abortController = new AbortController();

  async function startDeviceAuthentication() {
    const authentication = await client.authenticate([], "...", {
      authenticatorType: 'auto',
      userVerification: 'required',
      timeout: 60000,
      mediation: 'conditional',
      signal: abortController.signal,
    });

    // ...
  }

  void startDeviceAuthentication();

  return () => {
    abortController.abort();
  };
}, []);

As we add a new optional parameter, this should be backwards compatible.

dagnelies commented 4 months ago

Thanks

dagnelies commented 4 months ago

Hi, I merged this directly since it's backwards compatible ...but what's actually the use case for this abort signal?

JohnnyCrazy commented 4 months ago

When adding support for conditional UI to a SPA (like react), the signal can be used to cancel the currently active client.authenticate call. They implemented conditional UI as a long running promise, which needs to be awaited on. If you don't cancel it when navigating away from the login, future credential get/create calls fail because there is still one in progress. This is not needed when the navigation destroys the page context.

hjaber commented 4 months ago

I would utilize the abort signal also.

I initially call client.authenticate() and if it fails/client rejects, I then call client.authenticate() with the the option mediation: 'conditional' to allow autocomplete on an <input />, but the previous client.authenticate() must be aborted prior to calling again

dagnelies commented 3 months ago

@JohnnyCrazy @hjaber Hi guys ...I got another dumb question. Since only chrome and safari support this passkeys conditional UI, how do you deal with other browsers? Do you simply ignore them, or do you have some fancy fallback?

hjaber commented 3 months ago

@JohnnyCrazy @hjaber Hi guys ...I got another dumb question. Since only chrome and safari support this passkeys conditional UI, how do you deal with other browsers? Do you simply ignore them, or do you have some fancy fallback?

I do my best to make a fancy fallback but I mentally ignore the other browsers since there are a lot of edge cases to cover.

I call authenticate() with a try/catch and if it catches, I then call it with mediation: 'conditonal' which provides autocomplete on the input.

dagnelies commented 3 months ago

shouldn't it be the other way round? Direct authentication is more widely supported than conditional one.

For example, with Firefox and conditional mediation, it will simply do nothing. It won't throw an error. You have to check PublicKeyCredential.isConditionalMediationAvailable() in order to know if this autofill functionality can be used. And if you do use it, you have to abord it before calling registration for example.

dagnelies commented 3 months ago

Duh... PublicKeyCredential.isConditionalMediationAvailable() returns true for firefox ...but in reality it doesn't work 😆 (I tested right now)

hjaber commented 3 months ago

Apologies for the miscommunication!

My goal in implementing a fallback is primarily to enhance the UI/UX experience, not necessarily to broaden support for different authentication methods.

I do have an initial check that I did not mention to check if the client supports passkeys (isUserVerifyingPlatformAuthenticatorAvailable() or isConditionalMediationAvailable()). If not, I default to a magic link login.

If passkeys are supported, I then attempt direct authentication. However, due to the inherent advantages and disadvantages of this approach, I've included a fallback for mediation. If mediation isn't supported, the process falls back to a magic link login.

As you mentioned isConditionalMediationAvailable() returns true on firefox but doesn't work, my auth flow will still allow for a magic link login in this situation (since autocomplete won't popup on an input)

dagnelies commented 3 months ago

just wanted to clarify things to avoid potential readers being confused.

The conditional mediation is something you trigger when the page loads to "activate" the autocomplete on the input. If this passkeys autocomplete does not work (like on FF), the fallback could be to trigger authenticate(...) directly, without mediation.

A couple of additional details: