Authsome

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 ErrSMSNotAvailable when 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

OptionTypeDefaultDescription
CodeLengthint6Number of digits in the OTP
CodeTTLstring"5m"How long the code remains valid
MaxAttemptsint3Maximum verification attempts before the code is invalidated
AllowSignUpbooltrueAutomatically create new users for unknown phone numbers
RequireVerifiedPhoneboolfalseOnly allow sign-in if phone is already verified

API routes

All phone auth routes are registered under BasePath (default /v1/auth):

MethodPathDescription
POST/phone/send-codeRequest an SMS OTP for sign-in
POST/phone/verifyVerify OTP and sign in (or sign up)
POST/phone/verify-numberVerify phone number for an authenticated user

Security considerations

  • Rate limiting: Phone endpoints should be rate-limited to prevent SMS abuse. Configure RateLimit.PhoneCodeLimit to 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 MaxAttempts failed 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.

On this page