+15551234567) before storage or transport.
Endpoints
The user entity needs a
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. A misconfigured Twilio account then does not lock anyone out during early development. In production with a working Twilio config, dev_code is omitted and only the SMS carries the code.
Errors:
The code is 6 digits, expires in 10 minutes, and is rate-limited per-number to prevent SMS spam.
Verifying
displayName is optional. Pylon uses it when it creates a new User row because no existing row has this phone. On later sign-ins for the same number, displayName is ignored (the existing row’s name stays).
Errors:
On first successful verify Pylon stamps
phoneVerified to the current ISO 8601 timestamp.
Twilio transport (built-in)
The default SMS transport is Twilio. Set three env vars:/phone/send-code sends 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
Thepylon-auth crate exposes an SmsSender trait. It is the crate-level extension point for a non-Twilio provider (MessageBird, Vonage, Plivo, AWS SNS, or an internal SMS gateway):
pylon binary wires only the built-in Twilio transport. The /phone/send-code route handler calls it directly via TwilioSmsTransport::from_env(). The prebuilt binary has no env-var switch or registration hook to swap in a custom SmsSender. (PhoneCodeStore only generates and persists the 6-digit code. It returns the code to the caller, which delivers it; it does not hold an SmsSender.) To run a different provider today, build on the pylon-auth crate directly in your own Rust binary. Env-configurable third-party transports for the shipped binary are not yet available.
Security guarantees
- E.164 normalization on
phonebefore storage.(555) 123-4567,555-123-4567, and+15551234567collapse to the same canonical form. No two accounts share a 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 cannot exhaust the SMS budget. - Optional CAPTCHA gate — set
PYLON_CAPTCHA_PROVIDERto require a captcha token before send. See CAPTCHA. - Twilio credentials never logged. On transport failure only the provider error is logged at warn level (
[phone] twilio send failed: <error>). The SMS body and the code are never written to logs.
Where to go next
- Magic codes — email equivalent
- CAPTCHA — gate
/phone/send-codeagainst bot networks - Sessions — what
/phone/verifymints