Adding passkey authentication to a web app might sound straightforward until you start looking into it. The documentation you find is mostly aimed at people already familiar with browser security standards. Not exactly a smooth on-ramp when all you want to do is get passkeys working on your website.
This article is for developers who want to add passkeys without spending a week on the underlying cryptography. We will cover what passkeys are, why they are worth adding, and how to implement a working sign-up and sign-in flow using PlainKey.
What passkeys are
A passkey is a modern replacement for passwords. When a user registers with a passkey, their device generates a cryptographic key pair. The private key stays on the device and never leaves it. The public key is stored on your server. When the user wants to sign in, their device signs a challenge with the private key, and your server verifies it with the public key.
The user experience is simple: a fingerprint, a face scan, a PIN, or a hardware key like a YubiKey. No password to remember, no password to steal. Users tend to really like passkeys once they have registered one due to how frictionless it becomes.
A few things that make passkeys genuinely better than passwords:
- They are phishing-resistant. Passkeys are bound to a specific domain and will not work on fake login pages.
- Even if your server is breached, there are no passwords to steal. The private key never left the user's device.
- They are an industry standard, supported across all modern browsers and platforms - Chrome, Safari, Firefox, iOS, Android, Windows.
Passkeys are built on the WebAuthn standard, which is what makes them work everywhere. WebAuthn is also what makes implementing them from scratch a significant project.
What raw WebAuthn implementation involves
If you are curious why passkey services exist, this section gives you a sense of what they abstract away. If you would rather just get started, skip ahead to the next section.
Implementing passkeys directly against the WebAuthn API means your backend needs to generate and manage cryptographic challenges, define registration options with the correct algorithm types and authenticator settings, and send all of that to the frontend in precisely the right format. Registration alone requires four API endpoints - two for sign-up, two for sign-in - with browser interaction happening between each pair of calls.
On the verification side, you are dealing with CBOR-encoded attestation objects, Base64 versus Base64URL encoding (a common source of errors), authenticator data parsing, relying party hash verification, and credential counter tracking. There are good open source libraries like SimpleWebAuthn that handle chunks of this, but you are still responsible for the integration, the credential storage schema, and understanding enough of the spec to debug it when something goes wrong.
It is doable. It is also a meaningful project before you have written a single line of your actual application.
Adding passkeys with PlainKey
PlainKey is a European-hosted passkey service that handles all of the above for you. The integration has two parts: a small JavaScript library (SDK) that goes in your frontend, and a token verification step in your backend. Both have been designed to be as simple to use as possible.
To install the PlainKey browser library, run the following in your project terminal:
npm install @plainkey/browser
Sign up - registering a new user with a passkey
import { PlainKey } from "@plainkey/browser"
const plainKey = new PlainKey("YOUR_PROJECT_ID")
const { data, error } = await plainKey.createUserWithPasskey("user@example.com")
That single call prompts the browser to create a passkey, registers the user in PlainKey, and returns an authentication token. The browser handles the key generation and the user interaction entirely.
Sign in - authenticating an existing user
const { data, error } = await plainKey.authenticate()
Calling .authenticate() without parameters prompts the user to pick from any passkey stored on their device for your domain. For most sign-in flows, that is exactly what you want.
The backend step
Both calls return an authentication token. You pass that to your backend, where a single verification call to the PlainKey Server SDK or REST API returns the user's ID. You find or create that user in your own database, establish your session as you normally would, and you are done. Your user data stays in your own database throughout.
The full backend walkthrough is in the PlainKey docs.
What PlainKey provides
PlainKey handles the complex parts of passkey authentication: credential storage, challenge generation, signature verification, and the other details of WebAuthn. You use the PlainKey SDK and REST APIs to implement authentication on your site.
PlainKey stores the minimum amount of data about your users required for passkey management. You can even opt not to send any usernames or email addresses for your users at all.
What you handle: your app's UI, a few SDK calls, and your own user database and backend.
Getting started
Sign up at plainkey.io, create a project, and you will have a project ID within a minute. For local development, use localhost as your domain when creating the project.