Effortless security on the web

How to implement multi-device passwordless authentication on the web with the Web Authentication API

Effortless security on the web

Wouldn’t it be amazing if you could sign up with a website with face recognition on your mobile phone and the next day on your MacBook you could just use your fingerprint scanner to sign in? What if we told you that this is possible today?

Currently you manage your accounts using passwords that you have to remember. For most users this is done by either remembering various different passwords, or worse; using the same one for every website. While social login has brought us a convenient way to sign in to websites using a single account, it still requires an account with a password. On top of that, there are security and privacy risks when using a single account for each service. Extra layers of security, like two factor authentication, can be added. But those come at the cost of complexity for the end user. The problem lies in passwords themselves. To solve it we should, and finally can, get rid of them all together to provide a user-friendly and secure authentication flow.

Even though it is still early and it has not landed in all browsers, we feel this is the future. We have already looked into what it takes to implement. As with any form of authentication, it is definitely not a trivial subject and it requires effort to implement. But please bear with us though, as we talk you through the steps. The result for the end user is definitely worth it.

Passwordless authentication on the web is made possible by the new Web Authentication (WebAuthn) API. If you’re not familiar with this API, you can check out this guide or the video below to make it all a bit more tangible.

The simplicity of using WebAuthn

Connecting devices

The WebAuthn API supports replacing passwords with biometric data through device specific authenticators. These authenticators are, for example, the facial recognition & fingerprint scanners built into (mobile) devices.

Creating accounts and logging in with a device-specific authenticator has one problem. The fingerprint scanner on a mobile phone isn’t connected to the one on a laptop. So, how does a user access their account on a laptop if they created the account on a mobile phone?

For multi-device passwordless authentication we created a proof-of-concept flow. Our solution uses a secure environment already shared on all devices: email. A secure link sent through email connects the laptop & mobile phone. No typing required at all. Let’s explore this flow together.

Under the hood

For most server side languages, there’s a library available that does the heavy lifting around WebAuthn by implementing FIDO2. We went with abergs/fido2-net-lib on .NET core. The juicy part is in expanding the user interaction flow. Let’s start with registration.

A user enters a desired username (a valid email in our case), presses the register button and is presented with a biometric confirmation modal to resolve a generated challenge that is sent by the server. When the user successfully authenticates with their biometric info, an account is created for that user.

During account creation, these steps are executed:

  1. Server sends challenge
  2. Device requests biometric confirmation
  3. User scans finger
  4. Device creates public + private key
  5. Device sends public key as response
  6. Server creates user account associated to that public key

When a user later wants to login to the website, they enter their username (email) again and then are presented with a biometric confirmation modal to resolve a generated challenge. This time the server checks if the response (that the browser generates based on the given challenge and private key) matches the user with the given email address. If that’s the case, the user is logged in.

During user login, these steps are executed:

  1. Server sends challenge
  2. Device requests biometric confirmation
  3. User scans finger
  4. Device signs challenge with private key
  5. Device sends response to server
  6. Server verifies response with public key

On a technical level this is not too different compared to a regular password login, if you would hash and salt the password before sending it over. The biggest difference is we’re not asking the user to authenticate with something that needs to be remembered.

🇳🇱 👋 Hey Dutchies!
Even tussendoor... we zoeken nieuwe Q'ers!

Implementing multi-device sign-in

Logging in with a platform authenticator only works if the local device has credentials stored for this website. The first step in making multi-device possible is by detecting if the device has credentials for the current website user account or not. If it doesn’t, the Web Authentication API will throw an error. This allows us to ask the user if they want to add this device to their account.

let credentials;
try {
  credentials = await navigator.credentials.get(optionsObject);
} catch (err) {
  // Show modal to ask if the user wants to add this device to their account
  confirmAddDevice();
}
Detect if the device platform authenticator has known credentials

This is the hook that allows us to extend the user experience. For our take on a possible UX flow for passwordless multi-device authentication we came up with the diagram below. Highlighted in blue is the email confirmation flow that we found missing in existing implementations of logging in without a password. Remember that, while this diagram may seem daunting at first, all the parts in black are taken care of by WebAuthn and the FIDO2 library. Luckily that also is the hardest and most boring part.

Expand the platform authenticator flow to support multiple devices

Our flow starts when the user wants to sign in to their existing account from an unknown device. The flow starts similar to the regular login flow. However, we can now detect when the device has no credentials stored for this website.

Interaction wise this is where things get interesting. We can let the user know that this is an unknown account that is being attempted to sign in to. But just showing an error is a dead path. A user expects an immediate follow-up action to add this device to the account.

In our case we show a modal to ask the user if they want to add the new device:

async function confirmAddDevice() {
  const confirm = confirm('Do you want to add this device to your account?');
  if (!confirm) {
    // User denied the confirm modal, do nothing
    return;
  }

  try {
    await fetch('/api/add-device', {
      method: 'POST',
      body: {
        email: this.email
      }
    });

    alert('Email sent, click the link in the mail to continue the process.')
  } catch {
    alert('Could not send an email, please try again later.')
  }
}
Ask the user if they want to add the device to their account

When the user confirms the action, the client sends a request to the server. Luckily we know how to securely contact the user because they registered with their email as username. The server then sends an email that allows the user to register their device.

[HttpPost]
[Route("/add-device")]
public async Task<JsonResult> AddDevice([FromBody] string email)
{
  var user = Storage.GetUser(email);
  if(user == null) {
    return NotFound();
  }

  var otp = Storage.GenerateAndStoreOneTimePasswordForUser(user);

  var response = await SendAddDeviceEmail(user, otp);
  return Json(response);
}
Send an email to the user

By clicking the link in the email, the user confirms this device may be added to their account. The flow is just like with the first device, only adding a one time password (OTP) in the form of a link in an email. The OTP allows the new device to be securely linked to the existing account. This is done by the server, which checks if the OTP matches the one generated for the user, and stores the challenge response as an additional device linked to the account.

This way, minimal user input is required to allow multi-device passwordless authentication on the web. On top of that, we leverage existing patterns for account validation (through email) that users are already used to on the web.

Outro

We are very excited to work towards a more secure and effortless web, and can’t wait to use this in production. What’s your opinion on passwordless authentication, and how should multi-device usage be addressed? We would love to hear your ideas and get to know your implementations.


Do you love figuring out new features like multi-device passwordless authentication? Then please do check our job vacancies (in Dutch) at https://werkenbij.q42.nl!