hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Two-Factor and Passwordless Auth with @hectoday/http

Why Passwords Are Not Enough

  • The Problem with Passwords
  • Project Setup

TOTP (Time-Based One-Time Passwords)

  • How TOTP Works
  • Generating Secrets and QR Codes
  • Enabling 2FA on an Account
  • Verifying TOTP on Login
  • Time Windows and Clock Drift

Recovery

  • Recovery Codes
  • Disabling 2FA
  • Account Recovery When Everything Is Lost

Magic Links

  • How Magic Links Work
  • Building Magic Link Login
  • Security Considerations

WebAuthn and Passkeys

  • What Are Passkeys?
  • Registration Flow
  • Authentication Flow
  • Passkeys as Second Factor or Primary

Putting It All Together

  • Multi-Method Auth
  • Auth Method Checklist and Capstone

What Are Passkeys?

A fundamentally different approach

Passwords, TOTP, recovery codes, and magic links all share one property: the secret is known to both the server and the user (or the user’s device). If the server’s database is breached, secrets are exposed.

Passkeys are different. They use public-key cryptography: the user’s device generates a key pair (public key + private key). The server stores only the public key. The private key never leaves the device.

Authentication works by challenge-response: the server sends a random challenge, the device signs it with the private key, the server verifies the signature with the public key. The private key is never transmitted — not at registration, not at login, not ever.

Why passkeys are phishing-resistant

When a passkey is created, it is bound to a specific origin (domain). A passkey created for yourapp.com will only respond to authentication requests from yourapp.com.

If an attacker creates a phishing page at evil.com that looks identical to your login page, the browser will not use the passkey because the origin does not match. The user cannot be tricked into using their passkey on the wrong site, even if the page looks exactly right.

This is enforced by the browser, not by the user. The user does not need to check the URL — the browser does it automatically.

How it works at a high level

Registration (one-time setup):

  1. The server generates a random challenge
  2. The browser prompts the user to create a credential (fingerprint scan, Face ID, or PIN)
  3. The device generates a key pair and returns the public key + credential ID
  4. The server stores the public key and credential ID

Authentication (every login):

  1. The server generates a random challenge and sends it with the stored credential IDs
  2. The browser finds the matching credential on the device
  3. The user authenticates locally (fingerprint, face, PIN)
  4. The device signs the challenge with the private key
  5. The server verifies the signature with the stored public key
  6. If valid, the server creates a session

The local authentication (step 3) is what makes passkeys “something you have” (the device) combined with “something you are” (biometric) or “something you know” (PIN). But the biometric or PIN never leaves the device — only the cryptographic signature is sent to the server.

Platform vs. roaming authenticators

Platform authenticators are built into the device: Touch ID on Mac, Windows Hello, fingerprint on Android. The passkey is stored on that specific device.

Roaming authenticators are external hardware keys: YubiKey, Titan Security Key. They connect via USB, NFC, or Bluetooth and work across devices.

Modern passkeys can also sync across devices via cloud accounts (iCloud Keychain, Google Password Manager). A passkey created on your iPhone syncs to your Mac automatically. This solves the “lost device” problem that plagued earlier WebAuthn implementations.

The WebAuthn API

WebAuthn (Web Authentication API) is the browser API that implements passkeys. It provides two methods:

  • navigator.credentials.create() — registration (creates a new credential)
  • navigator.credentials.get() — authentication (signs a challenge with an existing credential)

On the server side, we use the @simplewebauthn/server library to generate challenges and verify responses. The next two lessons implement both flows.

Exercises

Exercise 1: Check if your browser supports WebAuthn: open the browser console and type typeof navigator.credentials.create. If it returns "function", your browser supports it.

Exercise 2: Research which authenticators your devices support. Does your laptop have a fingerprint reader or Face ID? Does your phone support passkeys?

Why is the private key never sent to the server?

← Security Considerations Registration Flow →

© 2026 hectoday. All rights reserved.