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:
- Browser hits
GET /api/auth/mfa/challenge/webauthn, which returnsPublicKeyCredentialRequestOptionsand a short-lived challenge cookie. - Browser calls
navigator.credentials.get(...)with those options. - Browser POSTs the resulting assertion to
/api/auth/mfa/verify/webauthn. - 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.