Authentication And Identity Systems

SMS Verification And Phone Login

SMS one-time codes are everywhere — phone-number login, "verify it's you", and a second factor on top of passwords. They are popular because a phone number is a near-universal identifier and the UX is familiar. They are also the weakest mainstream second factor, and knowing exactly why is what lets you use them where they fit and avoid them where they do not.

The Small-Keyspace Problem

An SMS code is short — typically six digits — because a human has to type it. Six digits is one million possibilities, which is nothing to a computer. The code's entire security therefore rests not on the code but on the controls around it:

  • Strict attempt limiting. A handful of wrong guesses per code, then the code is burned and a new one required. Without this, a million guesses cracks any code; with it, the attacker's odds are a few in a million per code. This is the single most important control, and its absence is the most common SMS-auth bug.
  • Short expiry. A few minutes. The code is a one-time credential, not a standing one.
  • Single use and single active code. Consumed on verification, and requesting a new code invalidates the old one, so an attacker cannot accumulate valid codes.
  • Server-side generation with a CSPRNG, stored hashed, compared in constant time — the same token hygiene as every other flow in this track. The code is short by necessity, so the surrounding discipline has to be tighter, not looser.

The send side needs its own limits, because sending SMS costs money and hits a real person's phone. Unlimited "resend code" is an SMS-bombing and toll-fraud vector — attackers trigger premium-rate or high-volume sends to inflate your bill or harass a target. Rate-limit per phone number and per account, and watch for the pumping pattern of many sends to numbers in one expensive region.

Why SMS Is A Weak Factor

Even implemented perfectly, SMS delivery is outside your security boundary, and it can be subverted without touching your system at all:

  • SIM swapping. An attacker social-engineers the mobile carrier into moving the victim's number to a new SIM, and now the codes arrive on the attacker's phone. This is a common, well-documented attack against high-value accounts, and no amount of application hardening prevents it.
  • SS7 and network interception. The telephone signaling network has known weaknesses that allow SMS interception in some circumstances.
  • Phishing relay. A fake login page asks for the code the user just received and replays it in real time — SMS codes are phishable because the user can read and retype them.

The consequence, and the current guidance: SMS is acceptable as a convenience factor or where a phone number genuinely is the account, but it should not be the only protection on a high-value account, and it is materially weaker than app-based TOTP or, best, the phishing-resistant passkeys in the next lesson. Offer stronger factors and let SMS be the fallback, not the ceiling.

Phone Numbers As Identity

If the phone number is the login (as in many consumer apps), model it like any other identity attribute with a twist: phone numbers get recycled by carriers and reassigned to new people. An account keyed purely on a phone number can be inherited by whoever gets the number next. Mitigate by treating a long-dormant number with suspicion, re-verifying periodically for sensitive accounts, and — as always — keying the durable identity off an internal user id, with the phone number as a verifiable, changeable attribute.

What To Check

Before moving on, make sure you can:

  • explain why a six-digit code's security depends entirely on attempt limiting
  • list the code controls: attempt limit, expiry, single-use, single-active, hashed
  • rate-limit the send side and explain the toll-fraud/bombing motive
  • name the out-of-band weaknesses: SIM swap, SS7, phishing relay
  • place SMS correctly: fallback factor, not sole protection for high-value accounts
  • handle phone-number recycling in phone-as-identity designs

What You Should Be Able To Do

After this lesson, you should be able to implement SMS verification with the strict controls its small keyspace demands, defend the send side against abuse, and make an informed choice about when SMS is an acceptable factor versus when a phishing-resistant method is required.

Practice

Practice: Design SMS Second-Factor
Show solution

Send side: rate-limited per phone number and per account (a few per hour, with escalating cooldown on "resend"), plus a global alarm for the toll-fraud signature of many sends to one costly region. This protects both the user (SMS bombing) and the bill (pumping).

Placement: SMS is offered as a second factor and the app offers TOTP and passkeys as stronger options, steering high-value users toward them. SMS is never the sole protection on an admin or billing-capable account; those require a phishing-resistant factor. Recovery codes (printed one-time backup codes) are issued when 2FA is enabled so a lost phone is not a lockout.

Identity note: the phone number is a verified, changeable attribute on an internal user id, not the identity key, so number recycling cannot silently transfer the account.

The design accepts SMS's known weaknesses (SIM swap, phishing relay) by scoping it to convenience-tier use and pairing it with better factors rather than pretending hardening fixes them.

Practice: Diagnose An SMS Bypass

Task

Explain both incidents and the controls that were missing.

Show solution

Two missing limits, two incidents.

Brute-forced account: a six-digit code is one million possibilities, and with no attempt limit an attacker simply submits guesses until one matches. Automated, that is minutes of work — the code's shortness is fine only because attempt limiting is supposed to make each code nearly unguessable in its short life. Without the limit, the small keyspace is fully exposed. Fix: a hard cap of a few attempts per code, after which the code is invalidated and a new one required; combined with the short expiry, this drops the attacker's success probability to a handful of chances per million.

SMS bombing: the unlimited "resend" button lets anyone (or an automated script) trigger unlimited texts to a target number — harassment for the victim and a toll-fraud bill for the company, especially if aimed at premium-rate destinations. Fix: rate-limit sends per number and per account with escalating cooldowns, and alarm on the pumping signature.

Both are the same underlying lesson from two sides: SMS auth's security lives entirely in the controls around the code, on both the verify side (attempt limit) and the send side (rate limit). The code itself provides almost no security on its own.

Practice: Define SMS Auth Checks
Show solution
  • A test brute-forces a code and asserts it is burned after the attempt limit — the single most important SMS test, since its absence is the classic bug.
  • Tests assert code expiry, single-use, and that requesting a new code invalidates the previous one.
  • A test confirms the send endpoint is rate-limited per number and per account, and a monitor watches send volume by destination region for the pumping signature.
  • A test confirms codes are generated with a CSPRNG and stored hashed.

Human, periodically:

  • Confirm SMS is not the sole factor on any high-value account type, and that stronger factors (TOTP, passkeys) are offered and encouraged — the review catches a new admin feature that shipped with SMS-only protection.
  • Review SMS spend and delivery metrics for abuse trends between incidents.
  • For phone-as-identity apps, confirm the number-recycling mitigations (dormancy checks, re-verification) are still in place.