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 codeThe 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
/challengecalls). 4th attempt returns429 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
| Code | Meaning |
|---|---|
VALIDATION_ERROR | Phone is not E.164 format |
MFA_PASSWORD_REQUIRED | OAuth-only users can’t enroll SMS — they rely on the IdP for security |
MFA_ALREADY_ENABLED | SMS is already verified for this user |
MFA_INVALID_CODE | Wrong code |
MFA_CHALLENGE_EXPIRED | More than 5 minutes since challenge was created |
MFA_RATE_LIMITED | Too many sends or wrong attempts |
MFA_SETUP_REQUIRED | Confirm 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/sdkdocs 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:
| Model | Number | Verification | Brand in SMS |
|---|---|---|---|
| Shared (default) | Aldero’s toll-free | Done once by Aldero | Your {tenant} verification code … Sent via Aldero. |
| BYO (Pro/Enterprise) | Your own toll-free or 10DLC | Each tenant verifies their own | Your {tenant} 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
- Aldero auth API reference
- Privacy Policy — describes how phone numbers are handled
- Terms of Service — SMS consent + carrier disclaimers
- TOTP MFA Guide (TBD) — authenticator-app MFA
- Email MFA Guide (TBD) — email-OTP MFA