skip to Main Content

I’m trying to implement a FIDO onboarding application for an app. To create the user credentials with WebAuthn API I use:

navigator.credentials.create(options)

This return a promise that resolves with user credentials when allowed. However, if instead of allowing, the user cancel the credentials creation, then a DOMException with with name NotAllowedError is thrown.

In the app, the credentials creation call to the Web Authentication API is triggered when the user clicks a button, that has an onClick which points to a function that also toggles a loading animation before attempting to create the credentials. In order to provide a good user experience, when the user clicks to cancel, I want to handle this exception, toggle the loading animation off and then display the button to create credentials again.

I want to know the best practices when handling this scenario. I don’t know enough about this flow to even know if it’s safe to wrap the call in a try-catch then handle every error with the name of NotAllowedError or if this Error is also thrown in other scenarios, for example, when failing to contact the user device, which in this case could cause the user that actually does want to allow credentials creation to be trapped in an infinite loop of clicking to create credentials, clicking allow, getting a NotAllowedError, having the button to create credentials displayed over again.

try {
    const credentials = await navigator.credentials.create(options)
    // do whatever
} catch (err) {
    if (err instanceof DOMException && err.name === "NotAllowedError") {
        // handle user cancelation
        return
    }
    // handle other scenarios
}

Long story short, I want to handle this scenario of user cancellation specifically and any other scenario I would handle the error some other way.

2

Answers


  1. NotAllowedError is returned in a number of situations, but they’re all broadly that the user abandoned the WebAuthn UI. The only other meaningful error for creation is InvalidStateError, which indicates that the user tried to create a credential on a platform authenticator which already has one of the credentials included in the exclude list.

    If your worry is that NotAllowedError might be returned synchronously, without user interaction, then that is possible. At least with Chromium, it can happen in cases such as when an already-inserted security key returns an invalid message

    Login or Signup to reply.
  2. Since NotAllowedError can occur in various situations where the WebAuthn UI interaction is incomplete or incorrect, it is important to handle this error in a way that does not assume it is always a direct user cancellation.
    You might want to include a message to the user indicating that the credential creation process was not completed, rather than explicitly stating that they cancelled it.

    As agl mentioned, handling InvalidStateError is also important.
    That error occurs when there is an attempt to create a credential that is already on the exclude list of the platform authenticator. That needs to be handled separately, possibly with a specific error message to the user.

    In scenarios where NotAllowedError might be returned synchronously (e.g., due to an invalid message from an already-inserted security key), it is still reasonable to catch and handle this within the try-catch block.
    However, additional logging or a more detailed error message could be helpful for debugging and user information.

    Your createCredentials function would be:

    async function createCredentials() {
        try {
            startLoadingAnimation();
    
            const credentials = await navigator.credentials.create(options);
            
            stopLoadingAnimation();
            // Handle successful credentials creation
        } catch (err) {
            stopLoadingAnimation();
    
            if (err instanceof DOMException) {
                if (err.name === "NotAllowedError") {
                    // Handle incomplete interaction with WebAuthn UI
                    // Use a generic message like "Process not completed"
                } else if (err.name === "InvalidStateError") {
                    // Handle the specific case of InvalidStateError
                    // Use a message like "Credential already exists"
                }
            } else {
                // Handle other errors
            }
        } finally {
            // Reset UI / Display appropriate messages
        }
    }
    

    That way, your error handling becomes more robust and takes into account the variety of scenarios that can lead to a NotAllowedError, as well as handling InvalidStateError specifically. That means a better user experience and aids in debugging and troubleshooting.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search