WRNexus
Back to the blog
security 5 min read

Why we default to passkeys (and not SMS) for MFA

SMS-based 2FA is better than nothing, but it has well-known weaknesses. Here is why WRNexus leads with passkeys.

Priya Iyer

#security · #webauthn · #mfa

When you turn on MFA in WRNexus, the first option we offer is a passkey — not SMS, not even a TOTP authenticator app. This is a deliberate, opinionated choice. Here is the reasoning.

SMS is phishable

The biggest attack against SMS-based 2FA is no longer SIM swapping (although that remains a real threat). It’s the humble phishing kit. The user types their password into a lookalike site, which forwards it to the real site, collects the OTP shown on their phone, and forwards that too. From the real site’s perspective, it received a valid login.

Passkeys defeat this attack at the protocol level. The signature the browser returns is bound to the origin (https://sso.wrnexus.com), so a lookalike like sso-wrnexus.com literally cannot ask the user’s authenticator to sign a valid challenge for the real domain.

TOTP is better, but still typeable

Authenticator apps remove the SIM-swap risk and don’t depend on a carrier network. But the user still has to read a code from one device and type it into another, which means a phishing kit can still relay it.

Passkeys never expose a value the user can read or type. The cryptographic exchange happens entirely between the authenticator and the relying party.

What about people without passkeys?

We support TOTP as a first-class second factor for everyone. We also issue ten one-time recovery codes the moment you enroll, so losing your phone isn’t a lockout event. SMS is the option we don’t offer — if you need it, that’s a signal you should be enrolling a passkey instead.

Implementation notes

Under the hood we use the WebAuthn Level 3 APIs through the webauthn Python package on the backend. The challenge flow is:

  1. Browser hits GET /api/auth/mfa/challenge/webauthn, which returns PublicKeyCredentialRequestOptions and a short-lived challenge cookie.
  2. Browser calls navigator.credentials.get(...) with those options.
  3. Browser POSTs the resulting assertion to /api/auth/mfa/verify/webauthn.
  4. Backend verifies the signature against the user’s registered public key.

The registration flow mirrors this with navigator.credentials.create(...) and a paired /api/mfa/passkeys/start and /api/mfa/passkeys/finish endpoint.

The end result: a user can enroll a passkey in about ten seconds, and sign in with a single tap from then on, while being immune to the most common phishing techniques on the open web.