Connor's Blog

Passkeys Are Not 2FA

A while back I wrote a post titled OTP in Your Password Manager Is Not 2FA about how password managers offering to store OTP secrets and generate codes is an anti-pattern.

This post is along similar lines, about something I’ve noticed when setting up a sign-in security key for my accounts on the web.

Let me give a concrete demonstration of the issue using Cloudflare.

The first thing that happens in the security key onboarding flow is that the Firefox extension for Proton Pass, which I use as my password manager, offers to set up a passkey. Bear in mind that I already have a passkey in this same password manager for signing in.

If I click the X to ignore that, then I get a native macOS dialog. It also offers to set up a passkey and store it in Keychain.

To set up my YubiKey, I have to click More Options.

It should be pretty obvious that using a passkey, which lives in the same password manager as your main sign-in password/passkey is not two factors. Setting it up like this would be pointless.

Passkeys shouldn’t be the default option when the site initiates the flow to onboard a security key.

I investigated a little bit and found that websites can set the following options when calling navigator.credentials.create to hint to the browser that only a physical security key should be onboarded.

const cred = await navigator.credentials.create({
  publicKey: {
    challenge,
    rp,
    user,
    pubKeyCredParams,
    authenticatorSelection: {
      authenticatorAttachment: "cross-platform",
      residentKey: "discouraged",
    },
    hints: ["security-key"],
    timeout: 60000,
    attestation: "none",
  },
});

With this, Proton Pass still suggests setting up a passkey, but that’s probably an issue with the extension.

It might seem like a petty complaint, but this stuff matters for security, especially for high-value targets who don’t necessarily have an understanding of how to protect themselves.