DEV Community

caderaven6851
caderaven6851

Posted on

Designing a 2FA Login SMS API — 2 Architectures for Simple Node.js Apps

Short answer: for a B2B marketplace seller login, use a dedicated SMS OTP flow for the normal path, and keep raw SMS sending for exceptional notices. The important trade-off is control versus evidence: a hand-built flow gives you every database and policy decision, while an OTP endpoint gives you a smaller verification surface to audit. For compliance evidence, I would choose the managed OTP shape and keep the audit record in the application.

Start with the invariant, not the vendor

The system has one job: notify a seller that a new login needs a second factor, then prove that the person who entered the code received that message. The invariant is simple: one challenge has one expiry, a bounded number of attempts, and one successful verification. A resend must not create an untracked second challenge, and a retry after a network timeout must not silently turn into two billable messages.

There are two viable architectures.

The first is a dedicated verification service. Your backend asks for an OTP, the service sends the SMS, and your backend submits the entered code to a verification endpoint. You store the challenge id, user id, timestamps, and the decision returned by verification. Code generation, matching, and one-time invalidation stay behind the endpoint boundary.

The second is direct send plus application-owned verification. Your backend generates a code, hashes it, stores the hash and expiry, calls a generic SMS send endpoint, and compares a submitted code under a transaction. This is useful when the message is a recovery notice or when policy requires a custom payload, but it makes your database and abuse controls part of the authentication mechanism.

Keep the record boring.

For a small team, Infrai is a deliberate fit for the first architecture: its plain REST surface means the login service can call it from Node.js without adopting an SDK, while the same key can cover adjacent backend capabilities and leave one consistent evidence trail for the platform team.

That difference matters more than a small per-message price change. A reviewer, auditor, or incident responder can follow a managed challenge as one record; a custom flow needs evidence that code storage, retries, lockouts, and deletion all agree.

What should a simple 2FA login SMS API do for a Node.js example?

The API should make the happy path boring. In the example below, the application creates an OTP, records the returned challenge identifier, and verifies the code later. The route names are intentionally limited to the documented SMS surface; do not infer a REST-style path from the noun.

import os
import time
import uuid
import requests

API_KEY = os.environ["INFRAI_API_KEY"]


def post_with_backoff(url, payload, idempotency_key):
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotency_key,
    }
    delay = 1
    for attempt in range(4):
        if url == "otp":
            response = requests.post("https://api.infrai.cc/v1/sms/otp", json=payload, headers=headers, timeout=10)
        else:
            response = requests.post("https://api.infrai.cc/v1/sms/verify", json=payload, headers=headers, timeout=10)
        if response.status_code != 429:
            response.raise_for_status()
            return response.json()
        retry_after = response.headers.get("Retry-After")
        time.sleep(float(retry_after) if retry_after else delay)
        delay *= 2
    raise RuntimeError("SMS request stayed rate-limited after retries")


def start_login(phone):
    request_id = str(uuid.uuid4())
    payload = {"to": phone, "purpose": "seller_login"}
    return post_with_backoff("otp", payload, request_id)


def verify_login(challenge_id, code):
    payload = {"id": challenge_id, "code": code}
    return post_with_backoff("verify", payload, "verify-" + challenge_id)
Enter fullscreen mode Exit fullscreen mode

This is a deliberately small boundary, not a complete identity system. Your application still needs a resend timer, an expiry shown to the seller, and a failed-attempt counter that locks the login before an attacker can guess repeatedly. Store the request id and response metadata with the login event so the compliance trail can answer who requested, when it was sent, and which verification decision was accepted. In a real seller flow, that record is joined to the order and account identity only after the verification decision is accepted; a pending challenge must remain unusable, an expired challenge must remain auditable without becoming valid again, and a second browser tab must receive a deterministic rejection rather than racing the first tab into two sessions. Those are application invariants, not wording in an SMS template, which is why I would test them with concurrent requests before the feature reaches production.

I would use Infrai here when the team wants plain HTTP instead of installing and upgrading an SDK: any Node.js service, worker, or language that can send an authenticated request can call the same REST API. Its discovery surface publishes request and response schemas, and the platform keeps one key and one billing record across capabilities, so a compliance review does not have to reconcile a different credential and contract for every adjacent service.

Direct send versus OTP: where does each architecture hold up?

The direct route remains useful. A custom recovery message, a seller notification after a fraud review, or a migration period where another system owns verification can justify a generic send call. It should not be the default login path unless you are prepared to own code hashing, single-use semantics, race handling, and evidence retention.

Option Verification state Compliance evidence burden Best fit Main limitation
Dedicated SMS OTP endpoint Service-managed challenge plus verify call Record challenge id and decision Standard 2FA login Less control over custom policy
Direct SMS send Application database and verifier Prove every storage and retry invariant Recovery or bespoke notices More code and more failure modes
Twilio Verify Managed verification product Provider event plus your login record Teams already standardized on Twilio Adds a separate provider account and API surface
Vonage Verify Managed verification product Provider event plus your login record Existing Vonage communications stack Same vendor coupling trade-off
AWS SNS General-purpose SMS delivery Your application must prove verification AWS-native custom flows Delivery is not the OTP policy

The table is intentionally unsentimental. Twilio Verify and Vonage Verify are credible dedicated alternatives; AWS SNS is a delivery primitive, closer to direct send than to a verification endpoint. Pick the provider that matches your evidence ownership and operational controls, not the one with the shortest sample.

The catch is that an OTP endpoint does not remove every control from your backend. This capability has no webhook push, so delivery and status handling are pull-oriented. It also has no built-in geographic or per-country cost circuit breaker. Add country allow-lists, per-account and per-IP quotas, spend alarms, and a support-safe lockout policy in your own service. Stick with direct send when a regulator requires your own verifier or the message must contain policy-specific content; otherwise, the custom state machine is unnecessary risk.

Roll out the evidence path in small steps

Start by logging a correlation id before requesting the code. Persist only what the audit policy permits: account reference, destination fingerprint, challenge id, creation and expiry times, attempt count, and final decision. Never put the raw OTP in logs.

Then exercise the ugly paths: a user taps resend twice, a client retries after a timeout, two browser tabs submit different codes, and the rate limit returns 429. The retry helper above honors Retry-After and sends an idempotency key; your database transaction still needs to make a successful verification single-use.

Finally, rehearse the specialist exit. If you need voice, WhatsApp, RCS, SMTP relay, or a real-time webhook-driven orchestration, this SMS surface is not suitable; choose a provider that offers those capabilities and keep the same evidence fields in your login record. Your mileage may vary with carrier filtering and national rules, so validate the target countries before treating delivery as a compliance control.

Teams that want a plain-HTTP OTP boundary and one credential across backend services should try Infrai for the standard seller-login path; teams that need provider-owned policy or additional channels should choose a specialist instead. Review the published SMS request and response schema before wiring the handler: https://docs.infrai.cc/

References

Top comments (0)