DEV Community

Cover image for How to add passkey login to your web app
Toby Hobson
Toby Hobson

Posted on • Updated on

How to add passkey login to your web app

Passkeys are the modern replacement for passwords, offering a secure and frictionless registration and authentication process. In this post I'll show you how to add passkey authentication to your web apps.

Authenticating with a passkey

Prerequisites

  1. The browser must have Javascript enabled.

  2. The browser must support the underlying APIs, in particular the Web Authentication API. Most browsers now support passkeys.

  3. The web application must be accessed in a secure context. The only exception to this is localhost, which can run over http.

  4. To prevent phishing attacks, passkeys are bound to a specific host/domain.

Getting started

We'll be using Passlock, a library and serverless backend. Passlock handles most of the heavy lifting.

Passlock has a zero lock-in policy - you can export your users' credentials in a standards compliant format. In a subsequent post I'll show you how to use those credentials with another popular library (simplewebauthn).

Install the library

You'll use Passlock on the client side, and REST calls on the server side. Go ahead and install Passlock into your client side bundle:

npm add @passlock/client

Obtain your identifiers

You'll also need to create a (free) Passlock account and obtain your Tenancy ID and Client ID. Sign into your Passlock console, navigate to development -> settings and note down the details.

Passlock settings

Register a passkey

Use the registerPasskey method to create a new passkey and register it on the user's device. This will return a secure token that you send to your backend for verification.

Register a passkey (frontend)

Assuming the user is logged into your app, you can offer them the opportunity to register a passkey:

import { Passlock } from '@passlock/client'

// tenancyId & clientId can be found in your 
// passlock console (settings tab)
const passlock = new Passlock({ tenancyId, clientId })

// assumes the user is already logged in
const principal = await passlock.registerPasskey({ email, firstName, lastName })

// send the token to your backend
console.log(principal.token)
Enter fullscreen mode Exit fullscreen mode

Send the token to your backend

Assuming everything went well, the principal will include a token property. You should send this to your backend for verification. How you do this is entirely up to you. You could use json/fetch or a simple hidden form field.

Register a passkey (backend)

Your backend now needs to call the Passlock API to verify the token and obtain the passkey details. You will need your Passlock API key to do this. This can be found in the API keys section of your Passlock console.

Make an HTTP GET request to the Passlock REST API, passing your token:

# Call https://api.passlock.dev/{tenancyId}/token/{token}
GET /{tenancyId}/token/{token} HTTP/1.1
Host: api.passlock.dev
X-API-KEY: 87gdq49a5-6s6pxseae-lnlu4ridv
Accept: application/json
Enter fullscreen mode Exit fullscreen mode

There's nothing special here, it's just an HTTP call with a custom header. You're free to use any tool or library to do this (Axios, Python requests etc). The response will include details about the Passkey, the most important of which is the subject.id:

{
  "subject": {
    "id": "khXCYCxcGwJTLoaG6kVxB"
  },
  "authStatement": {
    "authType": "passkey",
    "userVerified": false,
    "authTimestamp": "2024-01-25T12:01:07.295Z"
  },
}
Enter fullscreen mode Exit fullscreen mode

Link the user id

You should link the subject id to the user record in your own database e.g.

UPDATE User SET passlock_user_id = 'khXCYCxcGwJTLoaG6kVxB' WHERE id = 1;
Enter fullscreen mode Exit fullscreen mode

Passkey authentication

Use the authenticatePasskey method to trigger the authentication process. Just like the registration call, this will return a secure token.

Passkey authentication (frontend)

import { Passlock } from '@passlock/client'

const passlock = new Passlock({ tenancyId, clientId })

const principal = await passlock.authenticatePasskey()

// send the token to your backend
console.log(principal.token)
Enter fullscreen mode Exit fullscreen mode

Passkey authentication (backend)

The process is almost identical to the registration process. Simply exchange the token with the Passlock API, then use the subject.id to retrieve the user's record in your database. e.g.

SELECT * FROM User WHERE passlock_user_id = 'khXCYCxcGwJTLoaG6kVxB';
Enter fullscreen mode Exit fullscreen mode

Wrapping up

Adding Passkey authentication is not difficult. Passlock handles most of the heavy lifting - you just need to send a token to your backend, then exchange it with Passlock's REST API.

Next steps

When asking the browser to sign a challenge with a passkey, you can also ask it to re-authenticate the user locally. This will typically take the form of biometric authentication (FaceID, TouchID, or similar). Learn more about user verification in the Passlock docs.

I didn't cover error handling in this post. Quite a few things can co wrong: the device may not support passkeys, the user may try to re-register an existing passkey, or they might try to authenticate before creating a passkey.

Take a look at the Passlock tutorial to learn how best to handle these scenarios.

Questions or comments

Feel free to ask questions in the comments. I'll do my best to answer them.

Top comments (0)