+15551234567) before any storage or transport call.
Endpoints
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/api/auth/phone/send-code | POST | none | Send a 6-digit code via SMS |
/api/auth/phone/verify | POST | none | Verify the code; mints session, creates user on first sign-in |
phone field and optionally phoneVerified:
phone should be unique so two accounts can’t claim the same number.
Sending a code
dev_code field is returned whenever PYLON_DEV_MODE=true or the SMS transport returned an error — so a misconfigured Twilio account doesn’t lock anyone out during early development. In production with a working Twilio config, dev_code is omitted and only the SMS itself carries the code.
Errors:
| Status | Code | Reason |
|---|---|---|
| 400 | INVALID_PHONE | Phone is not valid E.164 (+ + 10-15 digits) |
| 400 | CAPTCHA_FAILED | CAPTCHA gate (when configured) rejected the request |
| 429 | RATE_LIMITED | Throttled — wait retry_after_secs |
Verifying
displayName is optional — used when Pylon needs to create a new User row because no existing row has this phone. On subsequent sign-ins for the same number, displayName is ignored (the existing row’s name stays).
Errors:
| Status | Code | Reason |
|---|---|---|
| 401 | INVALID_CODE | Wrong code, expired, or already burned |
| 400 | INVALID_CODE | Phone not valid E.164 |
| 429 | INVALID_CODE | Too many wrong attempts — the code is burned |
phoneVerified to the current ISO 8601 timestamp.
Twilio transport (built-in)
The default SMS transport is Twilio. Set three env vars and you’re done:/phone/send-code ships an SMS via Twilio’s REST API. When any are missing, sent is false and the dev_code is returned in the response.
Custom SMS providers
The framework’sSmsSender trait is the integration point. Wire your provider — MessageBird, Vonage, Plivo, AWS SNS, an internal SMS gateway — by implementing the trait and registering it as the active transport:
crates/auth/src/phone.rs — the framework reads the Twilio transport from env by default but PhoneCodeStore accepts any SmsSender impl.)
Security guarantees
- E.164 normalization on
phonebefore storage.(555) 123-4567,555-123-4567, and+15551234567collapse to the same canonical form — no two accounts for the same number. - 6-digit code, 10-minute TTL — same as magic codes.
- Code burns after wrong attempts —
try_verifyincrements an attempt counter; too many wrong attempts and the code is invalidated server-side. Status429 INVALID_CODE. - Constant-time code comparison — no timing leak on verify.
- Per-number rate limit on
send-codeso a phone-number enumeration attack can’t tie up SMS budget. - Optional CAPTCHA gate — set
PYLON_CAPTCHA_PROVIDERto require a captcha token before send. See CAPTCHA. - Twilio credentials never logged. SMS body text IS logged at warn level on transport failure for debugging.
Where to go next
- Magic codes — email equivalent
- CAPTCHA — gate
/phone/send-codeagainst bot networks - Sessions — what
/phone/verifymints