DEV Community

ethanbrooks1486
ethanbrooks1486

Posted on

Text OTP for Account Access: GDPR, PSD2, NIST, SIM-Swap and Phishing Exposure

Short answer: SMS OTP can be a useful step-up check for a low-risk login, but it is not enough by itself for every EU or US account, especially when a session can release a payment or expose a customer’s order history. Treat the phone number as one signal, record why it was accepted, and require a phishing-resistant factor for high-impact actions.

The concrete case here is a B2B SaaS app that sends an order receipt after payment settles. A user signs in, an operator reviews the order, and a worker sends the receipt. The login is not the only security boundary. The message, the payment event, and the audit record all have to line up.

The decision matrix I use before shipping

Situation SMS OTP role Better control Evidence to retain
New device, read-only dashboard Secondary check, with rate limits Device-bound or passkey factor when available Challenge result, device risk, timestamp
Editing a recipient address Step-up only if risk is low Phishing-resistant factor plus re-authentication Old and new value, actor, reason
Releasing a receipt after settlement Do not treat SMS as the sole approval Two-person approval or a stronger factor Payment event ID, approval IDs, message ID
Recovery after a lost phone Recovery path, not proof of identity Verified support process and backup factor Case ID, identity checks, reset event

My recommendation is deliberately boring: allow SMS OTP for the first tier, then make the action—not the login screen—the place where assurance rises. This keeps a routine sign-in quick without pretending that a text message defeats a SIM swap.

Keep it boring.

What does GDPR, PSD2, and NIST actually require from an SMS login?

These frameworks do not turn a six-digit code into a universal compliance stamp. GDPR asks for security appropriate to the risk and for accountability. That means you should be able to explain why a phone number was collected, how long challenge records live, and who can see them. Minimize the number itself in logs; a salted reference is usually more useful than printing a full number in every trace.

PSD2 strong customer authentication uses two independent elements from knowledge, possession, and inherence, with dynamic linking for applicable payment actions. A text code delivered to a handset may represent possession, but the risk assessment and the exact payment flow determine whether that is sufficient. A receipt job triggered after settlement is not the same event as authorizing the payment. Keep those events separate in the model.

NIST SP 800-63B classifies out-of-band authentication over a public mobile network as restricted. It calls out risks such as number reassignment, interception, and social engineering. “Restricted” is a design signal: plan a migration path and compensating controls instead of claiming that SMS is forbidden everywhere.

I am not a lawyer, and your mileage may vary by sector, supervisory authority, and transaction threshold. Have counsel map the exact processing purpose and payment journey; the engineering artifact should make that review easy.

How should a receipt workflow handle SIM swap and phishing risk?

Start with a threat model that names the asset. For this app, the asset is a correct receipt delivered to the intended business contact, not merely a successful login. An attacker who controls a phone can pass the OTP, change an address, and make the final message look legitimate. That sequence matters because each individual event can look normal in isolation: the carrier sees a valid number, the identity service sees a valid code, the payment worker sees a settled order, and the mail service sees a syntactically valid recipient. The useful detection point is the join between those events. Compare the tenant on the payment event with the tenant in the session, compare the recipient-change time with the authentication assurance, and require a fresh approval when the risk rises. If an audit reviewer cannot replay those joins from retained IDs, the control is hard to defend no matter how polished the login screen looks.

The worker therefore checks four independent facts: the settlement event is final, the order is bound to the tenant, the recipient change is recent or approved, and the authentication assurance meets the action’s tier. A password and an SMS code can still be phished together. A short code cannot tell the difference between a real sign-in page and a convincing copy.

Here is the narrow part I put in the service boundary. It records an OTP attempt without placing secrets in logs, and it refuses to send a receipt until the payment event and approval are both present.

type ReceiptDecision = {
  settled: boolean;
  tenantMatches: boolean;
  assurance: "sms" | "passkey" | "dual-approval";
  recipientChangedAt?: string;
  approvedBy?: string[];
};

export function canSendReceipt(input: ReceiptDecision): boolean {
  if (!input.settled || !input.tenantMatches) return false;
  if (input.recipientChangedAt && input.assurance === "sms") return false;
  if (input.assurance === "dual-approval") {
    return (input.approvedBy?.length ?? 0) >= 2;
  }
  return input.assurance === "passkey" || input.assurance === "sms";
}
Enter fullscreen mode Exit fullscreen mode

That function is not a compliance decision engine. It is a small, testable seam between authentication and messaging. I would add tests for replayed codes, a delayed settlement event, a tenant mismatch, and a recipient changed five minutes before the send. The exact five-minute window is a product policy, not a fact supplied by a standard; choose it from your threat model and document the choice.

The operational details that make the control defensible

Rate limits need more than an IP counter. Key them to account, destination, device, and challenge, then alert on a burst of failed attempts across many accounts. Expire a code quickly, invalidate it after one use, and avoid putting the account state in the SMS text itself.

Delivery telemetry matters too. CTIA guidance covers messaging interoperability and compliance practices, while SPF explains how a sender policy is published for email domains. Neither standard proves that an OTP login is safe, but both remind you to treat the message channel as an operational system with identity, policy, and abuse controls.

Keep an append-only audit record containing the decision, policy version, actor, payment event ID, and message provider reference. Do not retain the OTP value. On a support call, the useful question is “which policy allowed this receipt?” rather than “what code did the user type?”

There is a trade-off. A phishing-resistant factor costs more enrollment friction and needs a recovery story. SMS reaches more users, but its assurance is weaker and its dependencies sit outside your process. That is why I use it for bounded, reversible actions and require a stronger path before a recipient change or a sensitive export.

When is SMS OTP the wrong fit?

Do not use SMS as the sole gate for administrator recovery, high-value payment approval, or any flow where a stolen number can redirect money or regulated data. Stick with a passkey or hardware-backed factor when the account is privileged, the threat model includes targeted phishing, or policy requires a non-restricted authenticator.

SMS can still be the pragmatic fallback for a low-risk read-only session, a first contact check, or a carefully rate-limited recovery branch. Make that boundary visible in code and in the audit policy. The uncomfortable part is admitting that “two factors” describes a shape, not an assurance level.

References

Further reading

Top comments (0)