Passkeys / WebAuthn Library v2.0 is there! πŸŽ‰

Arnaud Dagnelies - Aug 13 - - Dev Community

Hello folks,

I'm pleased to announce the release of the v2.0 of my WebAuthn library!

This library greatly simplifies the usage of passkeys by invoking the WebAuthn protocol more conveniently. It is open source, opinionated, dependency-free and minimalistic (9kb only).

πŸ‘€ Demos

These demos are plain HTML/JS, not minimized. Just open the sources in your browser if you are curious.

πŸ“¦ Installation

Modules (recommended)

npm install @passwordless-id/webauthn
Enter fullscreen mode Exit fullscreen mode

The base package contains both client and server side modules. You can import the client submodule or the server depending on your needs.

import {client} from '@passwordless-id/webauthn'
import {server} from '@passwordless-id/webauthn'
Enter fullscreen mode Exit fullscreen mode

Note: the brackets in the import are important!

Alternatives

For browsers, it can be imported using a CDN link in the page, or even inside the script itself.

<script type="module">
  import {client} from src="https://cdn.jsdelivr.net/npm/@passwordless-id/webauthn@2.0.0/dist/webauthn.min.js"
</script>
Enter fullscreen mode Exit fullscreen mode

Lastly, a CommonJS variant is also available for old Node stacks, to be imported using require('@passwordless-id/webauthn'). It's usage is discouraged though, in favor of the default ES modules.

Note that at least NodeJS 19+ is necessary. (The reason is that previous Node versions had no WebCrypto being globally available, making it impossible to have a "universal build")

πŸš€ Getting started

There are multiple ways to use and invoke the WebAuthn protocol.
What follows is just an example of the most straightforward use case.

Registration

import {client} from '@passwordless-id/webauthn'
await client.register({
  challenge: 'a random string generated by the server',
  user: 'John Doe'
})
Enter fullscreen mode Exit fullscreen mode

By default, this registers a passkey on any authenticator (local or roaming) with preferred user verification. For further options, see β†’ Registration docs

Authentication

import {client} from '@passwordless-id/webauthn'
await client.authenticate({
  challenge: 'a random string generated by the server'
})
Enter fullscreen mode Exit fullscreen mode

By default, this triggers the native passkey selection dialog, for any authenticator (local or roaming) and with preferred user verification. For further options, see β†’ Authentication docs

Verification

import {server} from '@passwordless-id/webauthn'
await server.verifyRegistration(registration, expected)
await server.verifyAuthentication(registration, expected)
Enter fullscreen mode Exit fullscreen mode

Look at the docs for registration and authentication for the corresponding verification examples.
Or simply interact with real-life examples in the Testing Playground.

⁉️ Why a "Version 2"?

Nobody likes breaking changes, so what's the reason for it? The "Version 2" is not only a complete overhaul of the first version, it also differs from the previous mainly regarding its default behavior and "intermediate payloads". The said, it remains similar, striving for simplicity and ease-of-use.

A bit of ❀️ for security keys

Previously, this lib defaulted to using the platform as authenticator if possible. The user experience was improved that way, going straight to user verification instead of intermediate popup(s) to select the authenticator. It was a smooth experience.

This behavior was born from a time where credentials were always device-bound. The terms "synced credentials" and "passkeys" did not even exist when the initial version of this lib was made. Nowadays, most platforms / authenticators now sync credentials in the cloud. While this is certainly convenient, the security and privacy guarantees are not as strong as with device-bound credentials. That is why security keys now deserve some love, since they are the only ones providing such strong guarantees.

The new behavior is to let the user choose the authenticator by default. It's also the native protocol's default. We want to give the choice to use security by default, since they are more secure.

In order to better support for security keys, some defaults also changed, like the user verification now being preferred and being discoverable also being preferred. Both should allow a broader set of security keys to be usable by default.

Reflects latest protocol changes

The protocol evolved with time, and still is. For example, autocomplete with "conditional mediation" was added. It's an addition I'm not fan of, but certainly has its merits. It basically let's you trigger authentication using the autocomplete feature of a username input field. This is especially useful because there is no way to know if a credential has already been registered for the user or not. However, be wary that this feature is not universally supported for all platforms / browsers / authenticators.

Another recent change is the usage of hints ("security-key", "client-device", "hybrid"), which should replace the authenticatorAttachment and transports properties in the long term. They were kind of wacky anyway. That said, only Chrome supports hints for now, but it's handled in a backwards-compatible way by this library.

Payloads compatible with other server-side libs

This is another crucial large change. The response format has been changed completely to be compatible with the output as the PublicKeyCredential.toJson() method. An official part of the spec that only FireFox implements.

That way, it is possible to use the browser-side of this library and use almost any other server-side library for your favorite platform. Using this intermediate format increases compatibility cross-libraries in the long term.

πŸ™ Last words

The original project https://passwordless.id is currently in limbo between proof-of-concept and production-ready. It would simply take more time than I can afford. However, I wanted to wrap up the changes in this library, since the scope is way smaller, and it seems more used by a wider range of people. It also follows the same goal: getting rid of annoying passwords while providing more security. Although I wanted to achieve this goal on a grander scale, this library should at least help others to use passkeys more easily.

...but it's kind of crazy that I do this. Actually, I'm currently on holiday and I should rather play with my kids outside rather than writing this article. Well, I'm still kind of an old nerd too. I just wanted to wrap it up. Consider this lib stable, use it to your heart's content and if you want to help the open-source ecosystem keep its balance, consider sponsoring this project too.

Have a nice week,
Arnaud

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .