4 Things We Learned Supporting Passkeys

Passkeys have the potential to completely replace passwords, but it isn't perfect yet. Learn more about what you might encounter when supporting them.

 min. read
Last updated:
July 14, 2023

Do you know that the password “123456” is still used by more than 23 million people? People often think that hackers wouldn’t bother hacking their accounts and decide to use the same simple passwords on different platforms.

One of my friends told me his account was hacked, and malicious messages were sent to his Facebook friends. He also used a simple password similar to “12345678” for all his social accounts since he thought no one would hack his accounts. Sadly, it turned out that anything that could go wrong would go wrong,

Password is easy to use, making it the most common authentication method. However, people tend to use simple passwords that are vulnerable to cracking. Moreover, users may get tricked into giving hackers their passwords without knowing.

In 2022, we finally got good news! Industry leaders like Apple, Google and Microsoft are working on this new method of authentication called passkeys. Passkeys have the potential to replace passwords completely. Nevertheless, various compatibility and support issues exist before the technology is more mature.

In this blog post, we’ll briefly discuss passkeys' foundation and the problems we have encountered as we help developers easily support passkeys on their apps by Authgear.

What Are Passkeys?

Passkeys are digital credentials based on industry standard for user authentication. They follow the FIDO and WebAuthn standards that use public key cryptograpy, which is the most popular and secure protocol. Whenever a user creates a new account, a pair of public and private keys will be created. The public key is published, and the corresponding private key is kept secret, usually within a user’s device. The data encrypted with the public key can only be decrypted with the corresponding private key. Users simply have to unlock their devices with a PIN, biometric sensors, etc., to unlock the private key and gain access to the apps or websites. This way, users never have to reveal their private keys to anyone, unlike password-based authentication.

While tech giants work hard to bring passkeys to mobile devices, our team is working hard on supporting passkeys in Authgear so that everyone can easily enable login with passkeys on their apps. Since the support for passkeys is still far from mature, we encountered a couple of issues with integrating passkeys with various devices and platforms.

Here are the 4 issues for any developers trying to develop Passkeys-enabled websites and our solutions (and maybe why you might want to use Authgear instead of developing your solutions):

Needs to Maintain Backward Compatibility With Non-passkeys Supported Platforms

Before we got our hands dirty, we spent some time researching the compatibility of Passkeys on various platforms.

Before iOS 16, the platform authenticator like Safari creates single-device FIDO credentials. The implication is that while the end-user can create credentials, it will be cleared along with cookies. If the end-user signs up to your app with a single-device credential only, they will permanently lose access to their accounts when they clear their browsing history. Platforms like Android and Chrome desktop share this characteristic as well.

Single-device FIDO credential, cleared with the browser data

With Passkeys support on iOS16, Safari creates multi-device FIDO credentials stored in iCloud Keychain. Multi-device FIDO credentials are also known as Passkeys. The passkeys are still considered to have platform attachments, but these passkeys are synced across the end-user devices. Therefore, the passkeys are available on all devices the end-user owns and will not be cleared with the browsing history. This characteristic is also the key point of making passkeys usable among consumers.

On iOS16, Passkeys are synced across multiple devices with iCloud Keychain

As there are two kinds of credentials, “single-device credentials” and “multi-device credentials.” To ensure our end-users have the best experience, your app must be prepared to handle both technically.

That meant we needed to support multiple credentials per account, so the end-user can freely add credentials as they wish — add both Passkeys on supported platforms and single-device credentials on platforms without Passkeys support.

Autofill Prompts Supports Are Limited

Client-side discoverable credentials mean credentials that can be used without first identifying the end-user. The major use case of client-side discoverable credentials is to support autofill prompt — On the login page, you show a typical input field for end-users to type in their usernames; when they click on the input field, the system will prompt them with a list of available passkeys to use. The end-users can then tap to select a passkey and sign in instantly, making authentication much simpler.

Client-side discoverable credentials are quite impressive in terms of UX, but we encountered an issue that prevented us from rolling it out to our customers. The code to implement autofill is counter-intuitive. We need to create a pending promise while waiting for the autofill result. When the promise is settled, a new pending promise must be re-created. The pseudo-code looks like this:


function autofill() {
 // This function is recursive.
 // PublicKeyCredential.isConditionalMediationAvailable is available on iOS 16 and onward.
 // So autofill is only available on iOS 16 and onward.
 if (typeof PublicKeyCredential.isConditionalMediationAvailable ===  "function" {
  const available = await PublicKeyCredential.isConditionalMediationAvailable();
  if (available) {
   const options = // options are omitted for brevity.
   try {
    const response = await navigator.credentials.get({
     ...options,
     mediation: "conditional",
    });
    // Send the assertion response to your server to sign in.
   } catch (e) {
    // Inspect the error to see if we should recursively
    // call the function again.
     autofill();
    }
   }  
  }
}
    

The mediation option is the option that determines the behavior of the system. When mediation is conditional, the system does not show a modal dialog. On iOS 16, the available passkeys are shown as options in the keyboard accessory view. Thus, it works like autofill. When mediation is not specified, the system shows a typical modal dialog asking the user to select a passkey.

We ran into an issue that navigator.credentials.get({ mediation: "conditional"}) will immediately be rejected with a DOMException(name="NotAllowedError"). When such an exception is observed, the next invocation of navigator.credentials.get() will display the modal dialog normally. However, when the end-user chooses a passkey, the modal dialog becomes unresponsive, and the promise never settles. This bug effectively breaks the flow. We have no choice but to disable autofill for now. It’s probably a bug we have to wait til iOS 16 is officially available. This bug is tracked here.

Screenshot from WWDC video demonstrating autofill
Screenshot from WWDC video demonstrating modal dialog

User Experiences Vary Across Platforms

We rely on the WebAuthn API the platform/browser provides to use Passkeys. We have no control over the user interface presented to the end-user. If the platform does not offer helpful and explanatory error messages, the end-users could easily get stuck and not know how to proceed.

One such scenario happens on the Chrome desktop. Suppose the end-users sign up with a passkey already. When they attempt to log in, the navigator.credentials.get method supports an option allowCredentials to let us tell the device which passkeys the users can use.

When Safari finds that the credentials in allowCredentials do not match with the passkeys available, it will smartly ask the users to use another device by scanning a QR code or use a security key.

On Chrome desktop, however, the browser is not smart enough to hide the option of using credentials on the device. The end-user could tap on that option and see an unhelpful error message saying "Your identity could not be verified" without any other messages.

Chrome will let the user “Use the credentials on this device” even if the allowed passkeys are not found. And shows an unhelpful error message.

On the Firefox desktop, the modal dialog looks very similar to the ordinary permission dialog. The modal dialog is not centered and not big enough to draw the end-user’s attention. If the end-user is accustomed to the permission dialog, the modal dialog could be easy to miss.

The modal dialog looks very similar to the permission dialog and is easy to miss.

Error Handling Is Inconsistent Across Platforms

Though the specification does specify what error to throw in some exceptional situations, the granularity of the exception is up to name. Sometimes an exception with the same name is thrown, forcing us to look at the message to see what it means. The message is not specified, and all platforms have their proprietary messages. This makes us resort to matching the message with some regular expressions to guess the exceptional situations, which is quite tricky.

Conclusion

Though the integration involves two functions only, it is not an easy task if we are determined to make the experience of using passkeys as simple and easy as passwords. The difference in compatibility and the inconsistency of error handling between platforms are challenging for developers who wants to enable Passkeys on their web or apps.

Hand-rolling your implementation may not be good if robustness is crucial to your app and users. Start a free trial or contact us to see how you can benefit from Authgear and provide a frictionless experience for your users without all the hassles.