Passkeys / WebAuthn
Phishing-resistant biometric sign-in with passkeys. Radon runs the server-side ceremonies; your frontend calls the browser WebAuthn APIs. A Radon Pro feature.
Radon Pro
Passkeys are a Pro feature and require a license. They also need the
@simplewebauthn/server package on the server. See Radon Pro.
Passkeys let users sign in with Face ID, Touch ID, Windows Hello, or a security
key — no password, and phishing-resistant because the credential is bound to
your domain. Radon uses the audited
@simplewebauthn/server library server-side; it
never hand-rolls attestation parsing.
How passkeys are split between server and browser
WebAuthn needs browser APIs Radon can't call from the server. So every ceremony is two round-trips:
- Radon produces options (a challenge) → you send them to the browser.
- The browser calls
navigator.credentials.create()/.get()(or@simplewebauthn/browser) → you POST the result back to Radon to verify.
You must persist the challenge
Each start* call returns a challenge. Store it between the two round-trips (a
short-lived session/cookie) and pass it back to the matching finish* call.
Radon hands it to you rather than assuming where you keep state.
1. Enable the provider
npm install @simplewebauthn/server # server
npm install @simplewebauthn/browser # client (in your frontend app)export const auth = new Radon({
adapter: postgresAdapter(pool),
session: { secret: process.env.RADON_SECRET! },
licenseKey: process.env.RADON_LICENSE_KEY,
appName: "Acme",
providers: {
webauthn: {
rpID: "acme.com", // your registrable domain (no scheme/port)
origin: "https://acme.com", // full origin(s); string or string[]
},
},
});
await auth.init();| Option | Type | Description |
|---|---|---|
rpID | string | Required. Registrable domain, e.g. acme.com. |
origin | string | string[] | Required. Full origin(s), e.g. https://acme.com. |
rpName | string | Display name in the prompt. Defaults to appName. |
2. Registration (enroll a passkey)
The user must already be signed in (by any method) — passkeys attach to an existing user.
// POST /passkeys/register/start (your own route)
const { options, challenge } = await auth.webauthn.startRegistration(userId);
// Store `challenge` in the session, send `options` to the browser.
return Response.json({ options, challenge });import { startRegistration } from "@simplewebauthn/browser";
const { options, challenge } = await fetch("/passkeys/register/start", { method: "POST" }).then(r => r.json());
const response = await startRegistration(options); // prompts Face ID / security key
await fetch("/passkeys/register/finish", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ response, challenge }),
});// POST /passkeys/register/finish
const { credential } = await auth.webauthn.finishRegistration({
userId,
response, // the browser's result
expectedChallenge: challenge, // the one you stored
});
// Passkey stored. Done.3. Authentication (sign in with a passkey)
const { options, challenge } = await auth.webauthn.startAuthentication(userId);
// `userId` is optional — omit it for usernameless / discoverable-credential login.
return Response.json({ options, challenge });import { startAuthentication } from "@simplewebauthn/browser";
const { options, challenge } = await fetch("/passkeys/login/start", { method: "POST" }).then(r => r.json());
const response = await startAuthentication(options);
await fetch("/passkeys/login/finish", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ response, challenge }),
});const { user } = await auth.webauthn.finishAuthentication({
userId, // optional for usernameless login
response,
expectedChallenge: challenge,
});
const { token } = auth.createSessionToken(user.id); // sign them inManaging passkeys
const creds = await auth.webauthn.listCredentials(userId); // no key material exposed
await auth.webauthn.removeCredential(userId, credentialId);Next steps
OAuth (50 providers)
One generic OAuth 2.0 / OIDC engine powering 50 built-in providers — GitHub, Discord, Microsoft, Apple, and more. A Radon Pro feature.
2FA / TOTP
Add an authenticator-app second factor (Google Authenticator, 1Password, Authy) on top of any primary sign-in method. A Radon Pro feature.