Phone Authentication
SMS-based phone number verification and passwordless sign-in via one-time codes.
The phone authentication plugin enables users to sign in using their phone number and a one-time SMS verification code. It supports both passwordless sign-in (phone number is the only credential) and phone number verification as an additional identity attribute alongside email/password.
Setup
import (
"github.com/xraph/authsome"
"github.com/xraph/authsome/plugins/phone"
"github.com/xraph/authsome/bridge/smsadapter"
)
// Configure the SMS bridge (Twilio example)
smsSender := smsadapter.NewTwilioSender(
"ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Account SID
"your_auth_token", // Auth Token
"+15559876543", // From number
)
eng, err := authsome.New(
authsome.WithStore(store),
authsome.WithPlugin(phone.New()),
authsome.WithSMSSender(smsSender),
authsome.WithConfig(authsome.Config{
AppID: "myapp",
Phone: authsome.PhoneConfig{
CodeLength: 6, // number of digits in OTP
CodeTTL: "5m", // code validity window
MaxAttempts: 3, // max verification attempts per code
},
}),
)Note: An SMS bridge is required. Without it, the engine returns
ErrSMSNotAvailablewhen phone auth is attempted.
Phone sign-in flow
Phone-based sign-in is a two-step process: request a code, then verify it.
Request code: The user submits their phone number.
POST /v1/auth/phone/send-code
{
"phone": "+15551234567",
"app_id": "myapp"
}Response:
{
"status": "code_sent",
"expires_at": "2024-11-01T10:05:00Z"
}The engine generates a random 6-digit numeric code and sends it via the configured SMS bridge. The response always returns code_sent regardless of whether the phone number exists, preventing phone number enumeration.
Verify code: The user submits the code they received.
POST /v1/auth/phone/verify
{
"phone": "+15551234567",
"code": "847293",
"app_id": "myapp"
}Response (new user):
{
"user": {
"id": "ausr_01jb...",
"phone": "+15551234567",
"phone_verified": true,
"app_id": "aapp_01jb...",
"created_at": "2024-11-01T10:00:00Z"
},
"session_token": "a3f8c9d4...",
"refresh_token": "d72b1ef8...",
"expires_at": "2024-11-01T11:00:00Z"
}If the phone number is not associated with an existing user, a new user account is created automatically. If the phone number belongs to an existing user, a new session is created for that user.
Code generation and validation
Internally, the phone plugin uses the same SMS code infrastructure as the MFA plugin:
import "github.com/xraph/authsome/plugins/mfa"
// Generate a random numeric code
code, err := mfa.GenerateSMSCode(6)
// code → "847293"
// Send via the SMS bridge
challenge, err := mfa.SendSMSChallenge(ctx, smsSender, "+15551234567")
// challenge.Code → "847293"
// challenge.ExpiresAt → time.Now().Add(5 * time.Minute)
// Validate the submitted code
valid := mfa.ValidateSMSCode("847293", challenge)Phone number verification
When using phone as an additional identity attribute (not the primary sign-in method), users can verify their phone number through the same OTP flow:
POST /v1/auth/phone/verify-number
{
"phone": "+15551234567",
"code": "847293"
}On success, the user's phone_verified field is set to true and the phone field is updated on the user record. This is useful when phone was provided during signup but not yet verified.
SMS bridge
Interface
The SMS bridge is defined by the bridge.SMSSender interface:
type SMSSender interface {
SendSMS(ctx context.Context, msg *SMSMessage) error
}
type SMSMessage struct {
To string `json:"to"`
Body string `json:"body"`
}Twilio adapter
The built-in Twilio adapter sends SMS via the Twilio REST API:
import "github.com/xraph/authsome/bridge/smsadapter"
sender := smsadapter.NewTwilioSender(
"ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Account SID
"your_auth_token", // Auth Token
"+15559876543", // From number
)The adapter uses Basic Auth with the Account SID and Auth Token, and posts to the Twilio Messages API endpoint.
Custom SMS bridge
Implement the bridge.SMSSender interface to integrate with any SMS provider:
type MyCustomSender struct {
apiKey string
client *http.Client
}
func (s *MyCustomSender) SendSMS(ctx context.Context, msg *bridge.SMSMessage) error {
// Send via your SMS provider
payload := map[string]string{
"to": msg.To,
"message": msg.Body,
}
// ... make API call ...
return nil
}Then pass it to the engine:
eng, err := authsome.New(
authsome.WithSMSSender(&MyCustomSender{
apiKey: os.Getenv("SMS_API_KEY"),
}),
// ... other options
)Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
CodeLength | int | 6 | Number of digits in the OTP |
CodeTTL | string | "5m" | How long the code remains valid |
MaxAttempts | int | 3 | Maximum verification attempts before the code is invalidated |
AllowSignUp | bool | true | Automatically create new users for unknown phone numbers |
RequireVerifiedPhone | bool | false | Only allow sign-in if phone is already verified |
API routes
All phone auth routes are registered under BasePath (default /v1/auth):
| Method | Path | Description |
|---|---|---|
POST | /phone/send-code | Request an SMS OTP for sign-in |
POST | /phone/verify | Verify OTP and sign in (or sign up) |
POST | /phone/verify-number | Verify phone number for an authenticated user |
Security considerations
- Rate limiting: Phone endpoints should be rate-limited to prevent SMS abuse. Configure
RateLimit.PhoneCodeLimitto control the maximum number of code requests per phone number per window. - Code expiry: Codes expire after the configured TTL (default 5 minutes). Expired codes cannot be used.
- Attempt limits: After
MaxAttemptsfailed verifications, the code is invalidated and the user must request a new one. - Phone number format: Phone numbers should be submitted in E.164 format (e.g.,
+15551234567). The engine does not perform phone number normalization.