Skip to content

Passkeys or OTP?

How passkeys work, why they cannot be phished like an OTP, which devices support them, and how to add them next to the login you already have.

PasskeysUpdated 19 October 20267 min read

An OTP proves you can read a code sent to you. A passkey proves you hold a key made for this one website, unlocked by your fingerprint or screen lock. That difference is why passkeys are replacing codes for sign-in.

How a passkey works

  1. Registration. The phone or laptop makes a key pair for your domain. The private key stays on the device or in the user’s password manager. Your server stores the public key.
  2. Sign-in. Your server sends a random challenge. The device asks for the fingerprint or screen lock, signs the challenge with the private key, and your server checks the signature with the public key.

The browser only lets a passkey answer the domain it was made for. A look-alike phishing site gets nothing.

Passkeys vs OTP

SMS or email OTP Passkey
What the user does Waits for a code, types it One touch or glance
Can be phished Yes No
SIM swap risk Yes for SMS No
Cost per sign-in Rs 0.03 to Rs 0.45 Nothing to send
Works offline No Yes, on the device
Needs a new device to be set up No Yes, once per device or synced account

Where OTP still fits

  • Proving a new email or phone number at signup.
  • Recovery when someone loses every device.
  • Users on old devices without passkey support.

So the usual design is passkeys for sign-in, email OTP for signup and recovery.

Adding passkeys

The browser API is WebAuthn (navigator.credentials.create and .get). The work is on your server: making challenges, storing credentials and checking every response correctly. A minimal flow:

// Browser, with SignOps: registration token from your server, passkey made in the browser
import { passkeys } from "@signops/web";
await passkeys.register({ user_id: "usr_8f21c4", token: registrationToken });

// Later: sign in, with the passkey offered in the email field (autofill)
const { signops_token } = await passkeys.signIn({ autofill: true });
// your server confirms signops_token before starting a session

On Android, passkeys go through Credential Manager; on iOS through AuthenticationServices. Flutter and React Native packages wrap both.

SignOps Passkeys runs the server side on your own domain, with 10,000 sign-ins a month free and no limit on Starter, and email OTP on the same account for recovery.

Sources: passkeys.dev, Android: Credential Manager, W3C: Web Authentication.

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
Are passkeys better than OTP?

For sign-in, yes. A passkey only answers the website it was made for, so it cannot be phished, and there is no code to send, read or intercept. OTP is still useful to verify a new email or phone, and to recover an account when the passkey is lost.

Which phones support passkeys?

Android 9 and later, iOS 16 and later, and current Chrome, Safari, Edge and Firefox. Synced passkeys are backed up by Google Password Manager, iCloud Keychain or password managers such as 1Password.

Can I add passkeys to Flutter or React Native?

Yes. On Android they go through Credential Manager and on iOS through AuthenticationServices; Flutter and React Native packages wrap both, and your server must check the WebAuthn responses.

What happens if a user loses their phone?

Synced passkeys come back on the new phone through the same Google or Apple account. If not, let them in with an email code and ask them to make a new passkey.

Send your first push today.

Free for 1,000 push devices, 100 approvals a day, 10,000 passkey sign-ins a month and 500 email codes to try. No card until you pass a free amount.