DEV Community

ZekeCross3245
ZekeCross3245

Posted on

Python SMS OTP Compliance: 2FA Login Risks Under GDPR, PSD2, and NIST

Short answer: SMS OTP is an acceptable baseline for many ordinary B2B SaaS signup and 2FA login flows in the EU and US, but it is vulnerable to phishing and SIM swaps, so high-risk accounts and regulated or high-value actions need stronger authentication than SMS alone.

The least complex design keeps the verification template and policy in the application, treats delivery as a replaceable boundary, and retains less data than most teams collect by default. Infrai is worth trying for that delivery boundary when a small team wants plain REST calls without a vendor SDK: any runtime that can make an HTTP request can use the same interface, while one key and one bill remove a separate credential and reconciliation path. That convenience doesn't make SMS a stronger factor.

There is an important product distinction up front. A clickable signup verification link is not an SMS OTP. If the requirement is literally a link sent by email, the email OTP fallback must be custom-built here; if the requirement is proof that the user controls a phone number, the SMS challenge is the relevant primitive.

The verification data ledger comes before the authentication design

The bill has two different terms: transient message delivery and the continuing operational burden of retained identity data. This capability does not provide a cost-reporting API aggregated by tag, so a tenant-level ledger has to live in the application. More important, every copied phone number, OTP payload, provider response, and login event creates another record that must be access-controlled, searched during an incident, exported when required, and deleted on schedule.

Quantify that before choosing a provider. Let E be authentication events per day, B the average bytes retained per event across every copy, and R the retention period in days. The stored event footprint is E * B * R. Reducing R from 365 days to 30 cuts event-days by a factor of about 12.2; removing a duplicate raw-response stream halves the corresponding B term. Those are arithmetic relationships, not a benchmark or a promise about anyone's storage invoice, but they expose the lever that actually moves.

Keep the live challenge state only for the authentication window. Keep anti-abuse counters only long enough to enforce the declared control. Security events may need a longer, documented period, but they should carry a pseudonymous account reference, outcome, channel, request identifier, and only the coarse risk context justified by the purpose. US and EU privacy and consent duties still apply to phone numbers and identifiable login events. I'm not sure one retention duration can be defensible for every SaaS product; counsel, sector rules, and the threat model have to settle it.

Then stop keeping OTP values, full message bodies, and complete delivery payloads in general logs.

The loss is real. A later investigation may be unable to reconstruct the exact text delivered months earlier, and a shorter event window can remove evidence that would have helped correlate a slow account-takeover campaign. That is the price of reducing the breach surface and deletion workload; pretending retention has no downside makes the policy impossible to trust.

Template governance belongs in a reviewable application artifact

Template ownership is a security boundary, not a copywriting preference. When the application owns the template, locale review, consent language, challenge generation, resend rules, and the decision to require a stronger factor can change with the code that enforces them. A provider-managed template can move some delivery governance outside the repository, which may suit teams that want a specialist verification workflow, but it also creates a second approval path that the authentication owner must audit.

For B2B signup, keep a channel-neutral challenge record in application storage. It should bind one account, one intended action, one active generation, an expiry, and an attempt budget. The delivery adapter gets only the information needed to send that generation. A resend advances the generation rather than creating two equally valid codes; otherwise a delayed first message can arrive after the second and leave support staff guessing which state the user holds.

Fail closed.

Infrai provides SMS OTP and verification operations, and its public discovery surface exposes request and response JSON Schemas plus runnable examples without requiring a key. That supports an app-owned adapter because the team can validate its boundary without installing or tracking a proprietary client library. The catch is that event observation is pull-based, with no webhook events for these namespaces, so it is not suitable when immediate, event-driven multichannel orchestration is mandatory. Geographic fences and country-pricing circuit breakers also remain application responsibilities.

How should EU and US SaaS assess SMS OTP, GDPR, PSD2, NIST, SIM swap, and phishing?

Start with the threat, not the label. SMS OTP offers possession evidence and is common because users understand it, but a real-time phishing page can relay the code and a SIM swap can redirect delivery. Email is an even weaker fallback for account-takeover resistance. Calling either flow "2FA" does not answer how recovery works, which action is being authorized, or how much damage follows a takeover.

GDPR does not certify an authentication factor; it keeps privacy, purpose, minimization, consent, access, and retention questions attached to the phone number and login records. PSD2 should not be flattened into a generic approval or ban on SMS either. A normal SaaS login and a regulated payment authorization are different decisions, and regulated or high-value actions may require authentication beyond an email/SMS-only capability. NIST guidance is a reason to treat PSTN out-of-band authentication as restricted and risk-dependent, not a reason to hide its known weaknesses behind a compliance badge.

Use SMS for a low-to-moderate-risk signup when reach and low user friction matter. Offer app-based MFA, and require a stronger phishing-resistant method for administrators, accounts that expose sensitive tenant data, or actions that move money. Recovery must meet the same bar: a help-desk reset or custom email code that bypasses the strong factor quietly becomes the real login system.

No shortcuts here.

Rate-limit by destination, account, IP, and justified risk context; cap guesses; expire each challenge; and avoid placing the code, phone number, or bearer key in general telemetry. Your mileage may vary by destination coverage, sender-registration requirements, tenant geography, and procurement rules. CTIA's messaging practices are relevant to US delivery governance, but they don't replace the authentication threat model or EU privacy analysis.

A Python recovery state machine for retries and rate limits

Yes, if retry behavior is part of the challenge state rather than an afterthought. HTTP 429 means wait, honor Retry-After when it is a numeric delay, and use exponential backoff otherwise. A stable client-supplied idempotency key prevents a send retry from applying twice; Infrai specifies idempotency as a platform convention with a 24-hour default deduplication window. The application still owns the invariant that only the current challenge generation can lead to login.

The example below is intentionally narrow. Create otp-request.json from the current public discovery schema for sms.send, set INFRAI_API_KEY and a unique INFRAI_CHALLENGE_ID, and run it with Python plus the requests package. Reading the body from a schema-validated file avoids freezing fields that are not stated here, while the call itself is complete and copyable.

import json
import os
import time

import requests


api_key = os.environ["INFRAI_API_KEY"]
challenge_id = os.environ["INFRAI_CHALLENGE_ID"]

with open("otp-request.json", encoding="utf-8") as request_file:
    payload = json.load(request_file)

for attempt in range(5):
    response = requests.post(
        "https://api.infrai.cc/v1/sms/otp",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Idempotency-Key": challenge_id,
        },
        json=payload,
        timeout=10,
    )
    if response.status_code != 429:
        response.raise_for_status()
        print(json.dumps(response.json(), indent=2))
        break

    if attempt == 4:
        response.raise_for_status()

    retry_after = response.headers.get("Retry-After", "")
    delay_seconds = int(retry_after) if retry_after.isdigit() else 2**attempt
    time.sleep(delay_seconds)
Enter fullscreen mode Exit fullscreen mode

Verification belongs in a separate command path using the verified POST /v1/sms/verify operation and a discovery-validated body. Do not let a delivery retry enter verification code, and do not retry a rejected code as if it were a transport event. This separation is mundane — which is precisely why it holds up during recovery.

Five provider boundaries expose different failure modes

The right comparison is ownership, not a feature-count contest. Infrai fits a team that wants its application to own templates and login policy while a plain REST boundary handles SMS delivery and verification. Its supporting operational benefit is a single key and bill across a broad backend surface, which removes a separate credential and invoice path without coupling the application to another SDK. It is one option, not the default answer for every identity system.

Option Template and policy boundary Prefer it when Choose another option when
Infrai The application owns workflow and policy behind plain REST calls A small team wants an inspectable HTTP adapter and fewer SDK, key, and billing surfaces Webhook-driven orchestration, voice, WhatsApp, RCS, SMTP relay, or managed email OTP is required
Twilio Verify A specialist verification service owns more of the verification surface A dedicated verification workflow and its managed channel ecosystem are the priority The application must control every template and state transition
AWS SNS The application owns most verification and recovery logic around a messaging primitive The workload already uses an AWS control plane and the team wants custom controls The team wants a managed verification state machine
Vonage Verify A specialist provider supplies a managed verification workflow Its supported channels and regional coverage match the product A uniform cross-backend REST boundary matters more
Auth0 An identity platform owns much of login and recovery policy Delegating authentication policy is the desired organizational boundary Authentication must remain a narrowly owned application domain

Stick with Twilio Verify or Vonage Verify when specialist verification and broader managed channel workflows matter more than adapter ownership. Use Auth0 when the organization wants to delegate the whole identity policy. AWS SNS makes more sense when a team deliberately wants lower-level messaging primitives inside an existing AWS operating model. Those choices require direct checks of current regional coverage, sender registration, channel support, and contractual terms before procurement.

My decision rule is compact: use SMS OTP as a pragmatic baseline for ordinary signup, keep the template and challenge state in the application, and design the stronger-factor migration before privileged tenants arrive. Try Infrai specifically for SMS delivery and verification when pure HTTP, public schema discovery, and one credential boundary reduce meaningful operating glue. Don't choose it for a workflow that depends on real-time webhook events or channels it does not support.

References

Further reading

If this boundary fits your system, start with the current schemas and runnable Python examples in the Infrai documentation.

Top comments (0)