Set up passkeys on your website
Your domain, the sign-in page origins, a registration token from your server, the browser prompt and the check before a session starts.
A passkey lets a user sign in with the fingerprint, face or screen lock they already use to unlock their phone or laptop. It is saved by Google Password Manager, iCloud Keychain or the browser for your domain, and it cannot be typed into a fake site. This guide adds passkeys to a website that already has a sign-in. It takes about 30 minutes.
How it fits together: your server asks SignOps for short-lived tokens, the browser script talks to the user’s device, and your server checks the result before starting its own session. Your login system keeps its users and sessions.
1. Enter your domain in the dashboard
In the SignOps dashboard, open Passkeys, Setup:
- Domain: the domain users know, such as
kiranaexpress.in. Nohttps://, nowww. This is fixed once anyone has a passkey. - Name in the prompt: your product name, such as
Kirana Express. - Page origins: every address that shows your sign-in or “add a passkey” page, such as
https://kiranaexpress.inandhttps://app.kiranaexpress.in. They must be on the domain or a subdomain. Other origins are refused. - Allow synced passkeys: leave on. Off allows only passkeys tied to one device, which users lose with the device.
- Choose Save and turn on.
For local development, make a second SignOps app with the domain localhost and the origin http://localhost:3000.
2. Let signed-in users add a passkey
Offer it where users expect it: right after a sign-in (“Sign in faster next time with your fingerprint”) and on the account security page.
Your server makes a registration token for the signed-in user:
// Your server
app.post("/api/passkeys/token", requireUser, async (req, res) => {
const { token } = await signops.passkeys.registrationToken({
user_id: req.user.id, // your own user id
user_name: req.user.email, // what the user sees in their password manager
});
res.json({ token });
});
Your page passes it to the browser script:
import { configure, passkeys } from "https://api.signops.dev/web.js"; // or npm install @signops/web
configure({ key: "pk_live_..." }); // your publishable key
const { token } = await (await fetch("/api/passkeys/token", { method: "POST" })).json();
await passkeys.register({ token });
// The device's prompt appears; the passkey is saved for your domain.
3. Sign in with a passkey
Add webauthn to the email field’s autocomplete, so the browser offers saved passkeys as the user taps the field:
<input type="email" name="email" autocomplete="username webauthn" />
// On the sign-in page, at load
const { signops_token } = await passkeys.signIn({ autofill: true });
await fetch("/api/passkeys/signin", { method: "POST", body: JSON.stringify({ signops_token }) });
Add a Sign in with a passkey button that calls passkeys.signIn() without autofill, for browsers that do not offer autofill.
4. Check the token on your server
The token proves nothing until your server confirms it. Never start a session from the browser’s word alone.
app.post("/api/passkeys/signin", async (req, res) => {
const r = await signops.passkeys.verify(req.body.signops_token);
if (!r.valid) return res.status(401).end();
startSession(res, r.user_id); // your own session, as after a password
res.json({ ok: true });
});
A token works once and only for a short time, so a copied token cannot be replayed.
5. Try it
If you cannot reach your own site yet, the dashboard’s Try it here works on a test app with the domain signops.dev and the origin https://app.signops.dev. On your site: add a passkey, sign out, and sign in again by tapping the email field. Check what your browser supports with the passkey checker.
Then manage what you made: Users in the dashboard lists each user’s passkeys, and DELETE /v1/passkeys/:id removes one (for a “Remove this passkey” button on your security page).
Next
- A runnable Next.js app with these steps: nextjs-passkeys on GitHub
- The same passkeys in your Android and iOS apps
- With Better Auth or Supabase: the plugins end the passkey sign-in in their own session
- Why passkeys beat codes: Passkeys vs OTP