Skip to main content Passkeys: Why They Are Harder to Phish Than Passwords | IoT Worker

Passkeys: Why They Are Harder to Phish Than Passwords

Passkeys are often described first as “log in after a fingerprint scan.” The more important question is why they can suppress several of the most stubborn password risks at once. “It uses public-key cryptography” is not enough, because many systems use public keys and still fail against phishing, replay, and credential copying.

Passkeys sit on top of a protocol stack that works together: WebAuthn organizes registration and login, CTAP carries messages between the browser or operating system and the authenticator, and the authenticator signs the domain, challenge, and user-verification state together. What changes is not “password becomes private key.” What changes is how login is bound to the site, the request, and the user’s approval.

Where the Problem Comes From

Passwords are not only hard because users forget them. The deeper problems are:

  • Password reuse across sites
  • Servers storing reusable verification material
  • Phishing sites forwarding usernames, passwords, and OTPs in real time
  • Authentication success proving only that someone knew a secret, not that the secret was valid only for this site

Traditional MFA only patches this partially. TOTP may reduce reuse after a leak, but it cannot stop a user from typing the code into a phishing site.

FIDO2 and WebAuthn solve a lower-level trust boundary: the authenticator knows which relying party it is signing for, and the server stores only a public key instead of a shared secret.

Where It Came From

Passkeys are not a brand-new protocol that appeared out of nowhere. They are the latest deployment form of the FIDO stack for the Web. U2F and UAF came first, then WebAuthn standardized the browser-side interface, and CTAP filled in the client-to-authenticator path. Together they make what people now call FIDO2.

That history explains a few common misunderstandings:

  • Passkeys are not a separate new standard; they are a credential form and deployment model based on FIDO2
  • WebAuthn handles the browser and website boundary
  • CTAP handles the browser / operating-system to authenticator boundary

It also explains why passkeys may look inconsistent in practice: some credentials are synced, some are device-bound, some use built-in platform auth, and some use an external security key. Those are compatibility and deployment choices, not protocol confusion.

What They Actually Solve

In one sentence:

Passkeys create a per-site key pair and bind each login to this site, this request, and this user approval, while the server keeps only the public key.

The chain has four actors:

  • Relying Party (RP): creates the challenge and verifies the response
  • Client: browser or OS that passes the WebAuthn request to the authenticator
  • Authenticator: stores the private key, performs user verification, and signs
  • User: approves the operation with PIN, biometrics, or a touch gesture

Passkeys are about registration and authentication. They do not solve session expiry, authorization, risk policy, or account recovery by themselves.

Why They Are Designed This Way

The Main Path

At registration:

  1. The server creates a challenge and declares rp.id, user identity, and accepted algorithms
  2. The client passes that to the authenticator
  3. The authenticator creates a key pair for that RP and returns signed registration material
  4. The server verifies it and stores only the public key and credential ID

At login:

  1. The server creates a new challenge
  2. The client asks the authenticator for an assertion for the given rpId
  3. The authenticator verifies user presence or user verification and signs the authentication data
  4. The server verifies the challenge, domain binding, signature, and counter

The smallest skeleton is:

Registration:
RP -> Client: creation options(challenge, rp, user, algs)
Client -> Authenticator: makeCredential(...)
Authenticator -> RP: attestationObject + clientDataJSON

Authentication:
RP -> Client: request options(challenge, rpId, allowCredentials?)
Client -> Authenticator: getAssertion(...)
Authenticator -> RP: authenticatorData + signature + clientDataJSON

Site Binding

If one credential could be reused across sites, it would just recreate the password problem. WebAuthn’s critical choice is that each credential is naturally bound to an RP.

That gives you:

  • A site leak does not affect every other site
  • The protocol naturally splits credential use by site

The tradeoff is that credential management now has to handle users with multiple devices, multiple authenticators, and multiple sites.

What the Signature Actually Covers

The authenticator does not sign only a random challenge. It signs:

authenticatorData || SHA-256(clientDataJSON)

That binds three things at once:

  • The site
  • The request
  • The semantic type of the operation

The server should verify at least:

  • clientDataJSON.type
  • clientDataJSON.challenge
  • clientDataJSON.origin
  • authenticatorData.rpIdHash
  • the signature itself

Layered Responsibilities

WebAuthn faces the website and browser boundary. CTAP faces the client-to-authenticator boundary.

That split lets:

  • Websites avoid caring whether the authenticator is USB, NFC, BLE, or platform-native
  • Authenticators avoid dealing with page context directly
  • The ecosystem evolve without rewriting the whole web stack

It also means debugging has to ask where the failure is: WebAuthn, CTAP, or the authenticator itself.

Presence, Verification, and Counters

UP, UV, and signCount are not cosmetic fields.

  • UP says the user was present
  • UV says the user passed a stronger local check
  • signCount gives the server an anomaly signal if a credential may have been copied

Three Easy Misreads

Domain Binding

The anti-phishing property comes mostly from domain binding, not from public-key cryptography alone.

  • origin belongs to the browser security boundary
  • rpId belongs to the credential scope

They are related, but not the same.

Challenge

Without a fresh challenge for each login, a valid assertion could be replayed. The challenge must be created and consumed correctly.

Discoverable Credentials

Discoverable credentials change lookup and account selection. The authenticator can list matching credentials for the RP, which improves usability but also changes storage and account-management behavior.

Engineering Use

What to Verify First

First verify:

  1. Challenge freshness
  2. origin and rpIdHash
  3. Signature validity on authenticatorData || SHA-256(clientDataJSON)
  4. signCount behavior
  5. Whether attestation and credential extraction are handled correctly

Troubleshoot by Layer

When debugging, separate:

  • Server challenge handling
  • Browser origin / clientDataJSON
  • Authenticator capabilities
  • Registration flow (makeCredential) vs login flow (getAssertion)

Most Dangerous Assumption

Passkeys do not make the rest of account security disappear. They just change the authentication chain to one that is much harder to phish.

References