WebAuthn Components — Integrate Web Authentication using just Web Components

Bogdan Arvinte
ING Hubs Romania
Published in
8 min readJan 3, 2022
Cover image created by stories (https://www.freepik.com/stories)
Image created by stories (https://www.freepik.com/stories)

About Web Authentication

The Web Authentication API (or WebAuthn in short) is a specification written and published by W3C and the FIDO Alliance . Phishing and password attacks can become a thing of the past by leveraging the Credential Manager API that sits at the core of WebAuthn.

Web Authentication offers a better alternative for securing our sensitive online information and reduces the impact of data breaches. Public Key Cryptography has been around for half of a decade and is one of the mains pillars of modern online security.

There are three essential concepts behind Web Authentication:

  • a Hardware Security Module stores the private keys and performs cryptographic operations for authentication;
  • a key pair only works for a specific origin, thus preventing phishing;
  • authenticators provide a certificate that helps servers identify and confirm the source of the public key.

As far as authenticators are concerned, we can identify two main categories:

  • platform authenticators (built into the device)
  • roaming authenticators (an external device that can connect to other platforms)

The most common example for roaming authenticators would be the Yubikey, which has been around for several years. With it, users could add an extra layer of security to their online accounts by using the Yubikey as a second factor of authentication.

Since modern devices include a Hardware Security Model (TPM for laptops/PCs), platform authenticator support has improved over the past couple of years. The Web Authentication API has also matured and offers good integration across main browsers, thanks to the efforts of the FIDO Alliance and the contributions to the W3C specification.

Browser platform and roaming support

As of October 2021, the browser compatibility for platform and roaming authenticators looks like this:

Platform authenticator support

Roaming authenticator support

The Credential Manager API

To use the Web Authentication API, we need to be familiar with the Credential Manager API, and this is because all our interactions use two functions from navigator.credentials.

I encourage you to read the Guide to Web Authentication. It provides an excellent, easy-to-follow explanation of the steps involved with Web Authentication.

Registering credentials

To authenticate to a server using WebAuthn, we first need to create a key pair and upload the public key to the server, and we can do this using navigator.credentials.create().

Usual implementations of Web Authentication include two steps:

  1. We request some initial data from the server (a.k.a relying party) that we use as input for the navigator.credentials.create() function.
  2. We send the credential generated by navigator.credentials.create() back to the server to store our public key.

Authentication also uses two steps, with the difference that in the second step, we no longer provide a public key, but we include a signature of the challenge received in the first request.

The navigator.credentials.create function can contain one of the three options: a password, federated credentials, or public key credential creation options. In our case, we’ll be using the public key options, and it will look like this:

const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
});

We can easily tell that navigator.credentials.create() returns a promise, and that we'll need to provide some options for the publicKey. Here's an example of what those options might look like:

const publicKeyCredentialCreationOptions = {
challenge: Uint8Array.from(
randomStringFromServer, c => c.charCodeAt(0)),
rp: {
name: "Demo Service",
id: "demo-service.com",
},
user: {
id: Uint8Array.from(
idStringFromServer, c => c.charCodeAt(0)),
name: "MadMax",
displayName: "Max Kooky",
},
pubKeyCredParams: [{alg: -7, type: "public-key"}],
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
},
timeout: 60000,
attestation: "direct"
};

Most of the fields we can see above we get from the server (the Relying Party, rp in the options). The name and displayName can be optional or provided on the client side.

Some of the fields stand out, such as challenge and user.id. The specification dictates that those fields need to be in binary format, as that's how the Credential Manager API implementation works. Unfortunately, most REST server implementations send data over JSON, which cannot store binary data.

To get around this limitation of JSON, we take the String, convert it to ArrayBuffer, and create an Uint8Array view.

After the navigator.credentials.create() is executed successfully, we get back a PublicKeyCredential:

// PublicKeyCredential
{
id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
rawId: ArrayBuffer(59),
response: AuthenticatorAttestationResponse {
clientDataJSON: ArrayBuffer(121),
attestationObject: ArrayBuffer(306),
},
type: 'public-key'
}

This credential contains the public key and some other information for the server to validate the registration. Several fields in this credential use binary representation (ArrayBuffer), which means that before we send the credential back to the server, we need to transform the content into something that JSON can pass along.

Base64url encoding is a good fit for our case, and it also provides URL safety.

We can choose to implement the base64url transformations ourselves or use an existing library. Whichever the case, it adds a bit of complexity when dealing with the options and credentials. As I’ve mentioned in the introduction, I recommend familiarity with the Credential Manager API.

Web Components

There’s an interesting discussion on GitHub about using Web Authentication without JavaScriptw3c/webauthn/issues/1255. Native HTML support would be a great implementation. It would certainly help make WebAuthn ubiquitous, but until this discussion matures into a proposal, I think we could improve the DX with Web Components.

To this extent, together with a colleague, we’ve set out to boost the adoption of WebAuthn by providing a Spring Boot starter project, based on the Yubico implementation, and a collection of Web Components that are out of the box compatible with the Spring Boot starter. All resources are open-source software and we are happy to receive your feedback and contributions.

You can find both of them on GitHub, TechWebAuthn.

By using Web Components, developers only need to style the components to match their needs or requirements while still retaining the flexibility to make changes to the properties or tweak how certain things work.

It’s worth mentioning that the Web Authentication specification indicates that accessing the Credential Manager API requires user interaction (e.g., a button click or form submit). Therefore, all Web Components mentioned below include forms and use the form submit event to accommodate these requirements.

The following list of components supports all major ceremonies:

  • webauthn-registration — Used for creating a new account
  • webauthn-login — Used for connecting with a previously registered account
  • webauthn-recovery — Used to recover access to an account
  • webauthn-enrollment-requester — Used to request access to an existing account from a new device
  • webauthn-enrollment-provider — Used to confirm and provide access to a new device from the already registered device
  • webauthn-rtc-enrollment-requester — Similar to webauthn-enrollment-requester, but also makes use of WebRTC to send sensitive data between the devices
  • webauthn-rtc-enrollment-provider — Similar to webauthn-enrollment-provider, but also makes use of WebRTC to send sensitive data between the devices

Adding new devices to your account

While the first three ceremonies are pretty straightforward, adding additional devices to your account is only explained in the W3C specification with the help of a roaming authenticator.

Some users might not afford a roaming authenticator or want to carry it around everywhere they go. For these situations, we thought of an enrolment flow that only requires the user to have access to a previously registered device and the device that they wish to add.

There are also multiple ways of handling enrolment with just the two devices mentioned above, so we’ve included components for two methods:

A user could move/type/paste the generated token (used for linking the new device) from the previously registered device to the new machine they wish to enrol;

webauthn-enrollment-providerwebauthn-enrollment-requester

The generated tokens are large random strings encoded in Base64, which means they are not easy to type, and moving them across the internet (WhatsApp, Gmail, others) might not be a good idea.

A solution would be to use the relying party (server) to facilitate the communication between the two devices, which we can also implement in many ways. WebSockets are a good fit for this. For example, we could have the two devices join a room based on a code provided by the yet unenrolled device, and they could send the token and any other information through this WebSocket channel.

Depending on the service use case, keeping open many WebSocket channels might not be ideal. Thankfully we have another Web API at our disposal that could offload this communication, the WebRTC API. Using WebRTC, the two devices would only need an introduction for the discovery phase, using the WebSockets room. After that, they could leave the connection and continue the communication peer-to-peer.

webauthn-rtc-enrollment-providerwebauthn-rtc-enrollment-requester

Both these components include the code necessary to establish WebSocket and WebRTC communication.

Installation and usage

The components are all available as part of the webauthn-components package on npm and can be installed with a simple command:

npm install webauthn-components

All components are provided as ES Modules and include all required encoders and decoders as defaults.

To register a new account, we would import the required component:

import "webauthn-components/registration";

html`<webauthn-registration></webauthn-registration>` // lit-html

or straight in HTML

<script type="module" src="https://unpkg.com/webauthn-components/dist/webauthn-registration.js"></script>

<webauthn-registration></webauthn-registration>

The module also includes the Custom Element definition, which means that after we import the file, the webauthn-registration tag is ready to use.

Suppose the provided defaults do not suit your project or use case. In that case, you can customise just about everything, as the components expose events, properties for URLs, functions, and input attributes, as well as CSS parts for every element inside.

You could for example register without a username:

<webauthn-registration no-username></webauthn-registration>

or change the button text:

<webauthn-registration button-text="Create new account"></webauthn-registration>

We can also configure more complex properties:

<webauthn-registration></webauthn-registration>

<script>
const registration = document.querySelector('webauthn-registration');
registration.fetchOptions.headers['Cache-Control'] = 'no-cache';
registration.registrationStartUrl = '/api/v2/registration/start';
registration.registerCredentialEncoder = (credential) => { /* Some transformation */ }
</script>

And we can easily style any internal element by selecting its associated part:

webauthn-registration::part(button) {
border: none;
background-color: #14a;
color: #fcc;
border-radius: 0.5em;
}

More detailed documentation about the components is available on the project README, and you can also try them out on auth.marv.ro.

It’s never been easier to get started with Web Authentication, especially with platform support covering almost all vendor and system combinations and more and more platforms pushing for a passwordless web. W3C recently published a free course, titled Introduction to Web Authentication, on edX.

The next time you build a web application, consider implementing WebAuthn and maybe try out these Web Components. They’ll work in every major browser and play along nicely with any framework or library.

Resources

You can learn more about Web Authentication from the resources below:

--

--

Bogdan Arvinte
ING Hubs Romania

Web developer and tinkerer, passionate about all aspects of technology.