GuidesSMS Multi-Factor Auth

SMS Multi-Factor Authentication

Add SMS-based 2FA to your Aldero-powered authentication flow. Users enroll a verified phone number and receive a 6-digit code at login.

Try it live: /api/docs/demo — sign up and enable SMS Code under MFA settings.

How it works

Aldero is the registered sender of record on a single shared toll-free number. Your users opt in inside your application, and Aldero delivers OTPs on your behalf. SMS bodies identify both your tenant brand and Aldero:

Your Acme verification code is 123456. Sent via Aldero. Reply STOP to opt out, HELP for help.

This keeps one carrier registration covering all tenants — no per-tenant 10DLC paperwork needed.

Required: opt-in disclosure

Carriers require visible, explicit consent on the screen where users enter their phone. Your enrollment UI must include language equivalent to:

Verification codes are sent by Aldero, our authentication provider, on behalf of {your-brand}. Message frequency varies. Message and data rates may apply. Reply STOP to opt out, HELP for help.

A reference UI mockup is available at aldero.io/telnyx/sms-opt-in.png. The Aldero dashboard ships a working implementation at /dashboard/security/mfa you can copy from.

Endpoints

All endpoints live on the tenant subdomain: {slug}.aldero.io (or {slug}-sandbox.aldero.io for sandbox).

Enroll

# 1. User enters phone, you POST to /sms/setup
curl -X POST https://{slug}.aldero.io/v1/auth/mfa/sms/setup \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"phone": "+15550109911"}'
# → { "message": "Verification code sent via SMS.", "phone": "+15550109911" }
 
# 2. User enters the texted code, you POST to /sms/confirm
curl -X POST https://{slug}.aldero.io/v1/auth/mfa/sms/confirm \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
# → { "recoveryCodes": ["6dca-d1dd", "d417-fa55", ...] }

The user’s phone and phoneVerified fields are persisted automatically. Display the recovery codes once — they’re not retrievable later.

Login challenge

When a user with SMS enrolled logs in, the server responds with 403 mfa_required:

{
  "error": "mfa_required",
  "mfa_token": "<opaque>",
  "available_methods": ["sms"]
}

A code is auto-sent if SMS is the only enrolled factor. To explicitly request a fresh code:

curl -X POST https://{slug}.aldero.io/v1/auth/mfa/challenge \
  -H "Content-Type: application/json" \
  -d '{
    "mfa_token": "<token>",
    "challenge_type": "oob",
    "oob_channel": "sms"
  }'

Verify:

curl -X POST https://{slug}.aldero.io/v1/auth/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "mfa_token": "<token>",
    "method": "sms",
    "code": "123456",
    "trustDevice": true
  }'
# → { "accessToken": "...", "refreshToken": "...", "expiresIn": 900 }

trustDevice: true skips MFA on this device for 30 days via an HttpOnly mfa_device cookie.

Disable

curl -X DELETE https://{slug}.aldero.io/v1/auth/mfa/sms \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'   # TOTP code, or recovery code

The user’s phone field is not cleared — only the SMS factor is removed.

Rate limits

  • Send rate: 3 SMS per challenge (across login auto-send + manual /challenge calls). 4th attempt returns 429 MFA_RATE_LIMITED.
  • Verify rate: 5 wrong codes per challenge before the challenge is invalidated.
  • Throughput: pre-verification ≈ 500 messages/day; post-verification many thousands per minute (toll-free).

Errors

CodeMeaning
VALIDATION_ERRORPhone is not E.164 format
MFA_PASSWORD_REQUIREDOAuth-only users can’t enroll SMS — they rely on the IdP for security
MFA_ALREADY_ENABLEDSMS is already verified for this user
MFA_INVALID_CODEWrong code
MFA_CHALLENGE_EXPIREDMore than 5 minutes since challenge was created
MFA_RATE_LIMITEDToo many sends or wrong attempts
MFA_SETUP_REQUIREDConfirm called before setup

TypeScript SDK

import { AlderoClient } from '@aldero/sdk';
 
const aldero = new AlderoClient({ domain: 'aldero.io', tenantSlug: 'acme' });
 
// Enroll
await aldero.auth.mfa.sms.setup({ phone: '+15550109911' });
await aldero.auth.mfa.sms.confirm({ code: '123456' }); // returns recoveryCodes
 
// At login (after the mfa_required response)
await aldero.auth.mfa.challenge({ mfaToken, channel: 'sms' });
const tokens = await aldero.auth.mfa.verify({ mfaToken, method: 'sms', code: '123456' });

The SDK call shape may vary — check @aldero/sdk docs for the exact signature for your version.

Multi-tenant model deep-dive

If you’re operating multiple tenants on Aldero, you have two SMS sender models:

ModelNumberVerificationBrand in SMS
Shared (default)Aldero’s toll-freeDone once by AlderoYour &#123;tenant&#125; verification code … Sent via Aldero.
BYO (Pro/Enterprise)Your own toll-free or 10DLCEach tenant verifies their ownYour &#123;tenant&#125; verification code … (no Aldero mention)

The shared model is the default and what’s documented above. The BYO model is on the roadmap — open an issue or contact us if you need it before it ships.

Compliance

  • STOP / HELP keywords: handled by the carrier-side messaging profile. Reply STOP and the recipient is auto-removed from the messaging list — no code changes needed on your end.
  • Opt-in disclosure: must appear on the same screen where the user enters their phone (see required language above).
  • Recovery codes: must be displayed exactly once and never logged or stored unencrypted by your code.

See also