Why SMS OTP is the weakest MFA and what to use instead

SMS-based OTP is deployed by nearly every major web application as a second factor. It's familiar, requires no app installation, and works on any phone. These are real advantages — and they've driven adoption far beyond what the security of the mechanism warrants. When NIST Special Publication 800-63B relegated SMS OTP to "restricted authenticator" status in 2017 and warned agencies that its use should be carefully considered, the broader industry largely ignored the guidance. This post explains what NIST was seeing, and what you should offer users instead.

SIM swapping: social engineering the carrier

A SIM swap attack doesn't require any technical sophistication. The attacker calls your phone carrier's customer support, impersonates you, and convinces a representative to transfer your phone number to a SIM card they control. With your number, they receive your SMS OTPs. The weakness is entirely in the carrier's identity verification process — which has historically been poor enough that attackers do this at scale.

High-profile victims of SIM swapping have included Twitter's former CEO, multiple cryptocurrency exchange users losing millions in assets, and politicians. The FCC has taken action against carriers over insufficient protections. None of this changes the fundamental problem: SMS delivery relies on telco identity verification, which is not under your control as an application developer.

Your users with phone numbers listed in data broker databases (nearly everyone) are at elevated risk, because attackers can gather the answers to "security questions" from public records before making the call.

SS7 vulnerabilities

Signaling System 7 (SS7) is the protocol that underpins global telephone routing. It was designed in 1975 for trusted-party communication between carriers, with essentially no authentication between nodes. Researchers and intelligence agencies have demonstrated repeatedly that SS7 vulnerabilities allow interception of SMS messages in transit — an attacker with access to SS7 infrastructure (governments, some criminal organizations, and certain telecom insiders) can intercept any SMS message sent to any phone number.

This is not theoretical. A 2017 Motherboard investigation documented criminal use of SS7 interception to hijack WhatsApp accounts. The German Bundestag (federal parliament) was informed in 2017 that its members' SMS messages could be intercepted via SS7. For high-value targets, SS7 interception is a demonstrated real-world attack, not a theoretical concern.

Real-time phishing kits

SMS OTP is also defeated by adversary-in-the-middle phishing proxies. Tools like Evilginx and Modlishka sit between the user and your application, proxying requests in real time. The user believes they're on your login page, enters their password and SMS OTP, and the attacker's proxy forwards both credentials to the real site — completing authentication on behalf of the attacker before the OTP expires. The 30-60 second SMS OTP window is ample time for a real-time proxy to replay the code.

SMS OTP does not protect against real-time phishing proxies. Any authentication factor that travels over the same channel as the password — including SMS codes, email codes, and TOTP codes — can be intercepted and replayed by an adversary-in-the-middle. Only phishing-resistant authenticators (hardware keys, passkeys) provide cryptographic protection against this attack class.

NIST 800-63B guidance

NIST 800-63B Section 5.1.3.3 classifies SMS as a "restricted authenticator" and requires agencies that use it to provide at least one alternative authenticator, monitor for compromise, and document the use. The language is deliberately measured — NIST doesn't outright ban SMS OTP because it recognizes that some second factor is better than none. The guidance reads:

"Due to the risk that SMS messages or voice calls may be intercepted or redirected, implementers of new systems SHOULD carefully consider alternative authenticators."

NIST's concern is specific: the out-of-band channel (SMS) cannot be verified to be truly out-of-band when the same device is used to access the application and receive the SMS. A malware-infected phone that intercepts SMS is no barrier at all.

What to use instead

TOTP (RFC 6238): Time-based one-time passwords generated by an authenticator app (Authy, Google Authenticator, 1Password). The shared secret never leaves the enrollment process — OTPs are generated locally on the device. TOTP is still vulnerable to real-time phishing proxies but eliminates the SMS delivery attack surface entirely. Cost: requires users to install an app, which increases friction at enrollment.

Passkeys (WebAuthn/FIDO2): Phishing-resistant by design. The authenticator is cryptographically bound to the specific origin — a phishing site cannot get the user to authenticate to your real domain. The private key never leaves the device. Passkeys on modern platforms (iOS 16+, Android 9+, Windows Hello) require no app installation and integrate with the OS biometric system. This is the right long-term direction.

Hardware security keys (FIDO2/U2F): The gold standard. Physical key, phishing-resistant, cryptographically bound to origin. Appropriate for admin accounts and high-value users. Not practical as a mass-market requirement.

Migration path from SMS to TOTP

// Progressive migration: offer TOTP alongside SMS, then deprecate SMS
async function getMfaOptions(userId: string): Promise {
  const user = await db.users.findById(userId);
  const options: MfaOption[] = [];

  if (user.totpEnabled) {
    options.push({ type: 'totp', label: 'Authenticator app', recommended: true });
  }

  if (user.passkeyCount > 0) {
    options.push({ type: 'passkey', label: 'Passkey / biometrics', recommended: true });
  }

  if (user.smsEnabled) {
    options.push({
      type: 'sms',
      label: 'Text message',
      recommended: false,
      deprecationNotice: 'SMS authentication will be removed on March 1, 2025. Please set up an authenticator app.',
    });
  }

  return options;
}

// Nudge during login when user uses SMS
async function postSmsLoginNudge(userId: string, res: Response) {
  if (!await db.users.hasTotpOrPasskey(userId)) {
    return res.json({
      authenticated: true,
      nudge: {
        type: 'upgrade_mfa',
        message: 'Upgrade to an authenticator app for stronger security.',
        action: '/settings/security/mfa/enroll-totp',
        dismissable: true,
      }
    });
  }
  return res.json({ authenticated: true });
}

A three-phase migration works well: first, add TOTP enrollment as an option (show a banner encouraging users to upgrade); second, require new users to use TOTP or passkeys; third, set a deprecation date for SMS and send a series of warning emails before removing it. This gives existing users time to migrate without a forced disruption.