How to add passkey login to your web app
Tags: javascript, tutorial, webdev, passkeys
Published: 2024-03-18 16:25:28

Passkeys are the modern [replacement for passwords][why-passkeys], 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mq3mw775za6dlgcyeun.png)

## Prerequisites

1. The browser must have Javascript enabled.

2. The browser must support the underlying APIs, in particular the [Web Authentication API][webauthn]. Most browsers now [support passkeys][passkey-support].

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][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][signup] and obtain your `Tenancy ID` and `Client ID`. Sign into your Passlock console, navigate to development -> settings and note down the details.

![Passlock settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ukopjyjtw60obb6ifmt.png)

## 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:

```typescript
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)
```

### 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:

```http
# 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
```

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:

```json
{
  "subject": {
    "id": "khXCYCxcGwJTLoaG6kVxB"
  },
  "authStatement": {
    "authType": "passkey",
    "userVerified": false,
    "authTimestamp": "2024-01-25T12:01:07.295Z"
  },
}
```

### Link the user id

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

```sql
UPDATE User SET passlock_user_id = 'khXCYCxcGwJTLoaG6kVxB' WHERE id = 1;
```

## 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)

```typescript
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)
```

### 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.

```sql
SELECT * FROM User WHERE passlock_user_id = 'khXCYCxcGwJTLoaG6kVxB';
```

## 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][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][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.

[why-passkeys]: https://dev.to/toby_hobson/why-adopt-passkeys-5hk6

[passkeys]: https://developers.google.com/identity/passkeys

[webauthn]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API

[passkey-support]: https://passlock.dev/blog/passkeys/passkey-browser-support-2023

[passlock]: https://github.com/passlock-dev/passkeys

[signup]: https://console.passlock.dev/register

[console]: https://console.passlock.dev

[user-verification]: https://docs.passlock.dev/docs/info/user-verification

[tutorial]: https://docs.passlock.dev/docs/tutorial/intro

[replace-passwords]: https://dev.to/toby_hobson/using-passkeys-as-a-primary-login-mechanism-421o