serverpod_auth_sms

pub package

The recommended package for Serverpod SMS authentication.

中文文档

Why Use This Package?

This package provides complete SMS authentication functionality with flexible storage options:

What You Need Just Use This Package
Hash-only storage (irreversible) ✅ Use PhoneIdHashStore
Crypto storage (decryptable) ✅ Use PhoneIdCryptoStore
No manual hide Protocol, Endpoints ✅ Handled automatically

You do NOT need to import sub-packages separately. This package includes everything and lets you choose storage method via configuration:

// Just import this one package
import 'package:serverpod_auth_sms/serverpod_auth_sms.dart';

// Then choose your storage method:
final phoneIdStore = PhoneIdHashStore.fromPasswords(pod);   // Hash-only
// OR
final phoneIdStore = PhoneIdCryptoStore.fromPasswords(pod); // Crypto (recommended)

Sub-packages (_core, _hash, _crypto) are for advanced users only and require manual hide Protocol, Endpoints directives. Most users should just use this package.

Features

  • SMS Registration - Register with phone number and verification code
  • Verification Code Login - Login with phone + code (auto-register unregistered users)
  • Phone Binding - Bind phone to existing accounts
  • Provider-Agnostic - Works with any SMS provider via callbacks

Installation

Server Dependencies

# gen_server/pubspec.yaml
dependencies:
  serverpod_auth_sms: ^0.1.6
  serverpod_auth_sms_core_client: ^0.2.0  # Recommended: reuse shared password policy helper
  # Optional: Tencent Cloud SMS integration (for China business)
  tencent_sms_serverpod: ^0.2.0

Client Dependencies

# gen_client/pubspec.yaml
dependencies:
  serverpod_auth_sms_core_client: ^0.2.0
  # Add ONE of the following based on your storage choice:
  serverpod_auth_sms_crypto_client: ^0.1.6  # For crypto storage
  # serverpod_auth_sms_hash_client: ^0.1.6  # For hash storage

Quick Start

Step 1: Choose Storage Method

Storage When to Use Configuration Required
Crypto (recommended) Most cases - can verify AND retrieve phone numbers phoneHashPepper + phoneEncryptionKey
Hash Maximum privacy - can only verify, cannot retrieve phoneHashPepper only

Note: Crypto storage internally stores hash values too, so it covers all hash functionality while also supporting phone number decryption. If you're unsure, choose crypto - you can always verify phones (like hash), plus decrypt when needed (e.g., customer support, notifications).

Step 2: Configure passwords.yaml

# config/passwords.yaml
shared:
  # ============================================
  # Core SMS Authentication (Required)
  # ============================================

  # Pepper for hashing verification codes (required)
  smsSecretHashPepper: 'your-random-string-for-verification-code-hashing'

  # ============================================
  # Phone Number Storage (Required)
  # ============================================

  # Pepper for hashing phone numbers (required for BOTH hash and crypto)
  # WARNING: Cannot be changed after deployment - existing data won't match
  phoneHashPepper: 'your-random-string-for-phone-hashing'

  # AES-256 encryption key (required ONLY for crypto storage)
  # Generate with: openssl rand -base64 32
  # WARNING: Cannot be changed - leakage allows decryption of all phone numbers
  phoneEncryptionKey: 'base64-encoded-32-byte-key'

  # ============================================
  # Tencent Cloud SMS (Optional - for China business)
  # ============================================

  tencentSmsSecretId: 'your-tencent-secret-id'
  tencentSmsSecretKey: 'your-tencent-secret-key'
  tencentSmsSdkAppId: '1400000000'
  tencentSmsSignName: 'YourAppName'
  tencentSmsRegion: 'ap-guangzhou'  # Optional, defaults to ap-guangzhou

  # Template configuration (choose ONE method):
  # Method 1: Direct template ID
  tencentSmsVerificationTemplateId: '123456'

  # Method 2: Scene-specific templates
  # tencentSmsVerificationTemplateNameLogin: 'LoginTemplate'
  # tencentSmsVerificationTemplateNameRegister: 'RegisterTemplate'
  # tencentSmsVerificationTemplateNameResetPassword: 'ResetTemplate'

  # Method 3: CSV template mapping
  # tencentSmsTemplateCsvPath: 'config/sms/templates.csv'

Step 3: Configure Server

import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_sms_core_client/serverpod_auth_sms_core_client.dart'
    show validateAuthPasswordPolicy;
import 'package:serverpod_auth_sms/serverpod_auth_sms.dart';

void run(List<String> args) async {
  final pod = Serverpod(args, Protocol(), Endpoints());

  // ========================================
  // Choose ONE storage method:
  // ========================================

  // Option A: Crypto storage (RECOMMENDED)
  // - Can verify phone numbers (like hash)
  // - Can also decrypt to get original phone (for support, notifications, etc.)
  final phoneIdStore = PhoneIdCryptoStore.fromPasswords(pod);

  // Option B: Hash storage
  // - Can only verify phone numbers
  // - Cannot retrieve original phone (maximum privacy)
  // final phoneIdStore = PhoneIdHashStore.fromPasswords(pod);

  pod.initializeAuthServices(
    tokenManagerBuilders: [JwtConfigFromPasswords()],
    identityProviderBuilders: [
      SmsIdpConfigFromPasswords(
        phoneIdStore: phoneIdStore,
        sendRegistrationVerificationCode: _sendSmsCode,
        sendLoginVerificationCode: _sendSmsCode,
        sendBindVerificationCode: _sendSmsCode,
        passwordValidationFunction: validateAuthPasswordPolicy,
      ),
    ],
  );

  await pod.start();
}

// Implement your SMS sending logic
Future<void> _sendSmsCode(
  Session session, {
  required String phone,
  required UuidValue requestId,
  required String verificationCode,
  required Transaction? transaction,
}) async {
  // Call your SMS provider API here
  await yourSmsClient.send(phone, verificationCode);
  session.log('Sent code $verificationCode to $phone');
}

Recommended: use validateAuthPasswordPolicy from serverpod_auth_sms_core_client directly so Server config, Flutter rule hints, and tests share the same source of truth. If you need extra business rules, wrap this helper instead of reimplementing the whole password policy.

With Tencent Cloud SMS (for China Business)

import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_sms_core_client/serverpod_auth_sms_core_client.dart'
    show validateAuthPasswordPolicy;
import 'package:serverpod_auth_sms/serverpod_auth_sms.dart';
import 'package:tencent_sms_serverpod/tencent_sms_serverpod.dart';

void run(List<String> args) async {
  final pod = Serverpod(args, Protocol(), Endpoints());

  // Create Tencent Cloud SMS client (credentials from passwords.yaml, other config passed directly)
  final smsConfig = TencentSmsConfigServerpod.fromServerpod(
    pod,
    appConfig: TencentSmsAppConfig(
      smsSdkAppId: '1400000000',
      signName: 'YourSignName',
      templateCsvPath: 'config/sms/templates.csv',
      verificationTemplateNameLogin: 'Login',
      verificationTemplateNameRegister: 'Register',
      verificationTemplateNameResetPassword: 'ResetPassword',
    ),
  );
  final smsClient = TencentSmsClient(smsConfig);
  // Use Chinese error messages: TencentSmsClient(smsConfig, localizations: const SmsLocalizationsZh())
  final smsHelper = SmsAuthCallbackHelper(smsClient);

  // Choose storage method (see Step 1)
  final phoneIdStore = PhoneIdCryptoStore.fromPasswords(pod);

  pod.initializeAuthServices(
    tokenManagerBuilders: [JwtConfigFromPasswords()],
    identityProviderBuilders: [
      SmsIdpConfigFromPasswords(
        phoneIdStore: phoneIdStore,
        sendRegistrationVerificationCode: smsHelper.sendForRegistration,
        sendLoginVerificationCode: smsHelper.sendForLogin,
        sendBindVerificationCode: smsHelper.sendForBind,
        passwordValidationFunction: validateAuthPasswordPolicy,
        // Optional configurations:
        requirePasswordOnUnregisteredLogin: true,
        allowPhoneRebind: false,
        verificationCodeLength: 6,
        loginVerificationCodeLifetime: Duration(minutes: 10),
      ),
    ],
  );

  await pod.start();
}

Step 4: Create Endpoints

// lib/src/endpoints/sms_idp_endpoint.dart
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_sms/serverpod_auth_sms.dart';

class SmsIdpEndpoint extends SmsIdpBaseEndpoint {}

// lib/src/endpoints/phone_bind_endpoint.dart
class PhoneBindEndpoint extends SmsPhoneBindBaseEndpoint {}

// lib/src/endpoints/sms_auth_ui_endpoint.dart
class SmsAuthUiEndpoint extends SmsAuthUiBaseEndpoint {}

Database Migration

After adding dependencies, generate and apply migrations:

cd your_server_project
serverpod create-migration
dart bin/main.dart --apply-migrations

Tables created:

  • serverpod_auth_sms_account - User credentials
  • serverpod_auth_sms_account_request - Registration requests
  • serverpod_auth_sms_login_request - Login requests
  • serverpod_auth_sms_bind_request - Binding requests
  • serverpod_auth_sms_phone_id_crypto or serverpod_auth_sms_phone_id_hash - Phone storage

Configuration Options

Feature Toggles

Option Description Default
enableRegistration Enable SMS registration true
enableLogin Enable verification code login true
enableBind Enable phone binding true
requirePasswordOnUnregisteredLogin Require password for auto-register true
allowPhoneRebind Allow changing bound phone false

Verification Code Settings

Option Description Default
verificationCodeLength Code length 6
registrationVerificationCodeLifetime Registration code TTL 10 min
loginVerificationCodeLifetime Login code TTL 10 min
bindVerificationCodeLifetime Bind code TTL 10 min

Rate Limiting

Option Description Default
registrationVerificationCodeAllowedAttempts Max registration attempts 5
loginVerificationCodeAllowedAttempts Max login attempts 5
bindVerificationCodeAllowedAttempts Max bind attempts 5
registrationRequestRateLimit Registration rate limit 5/10min
loginRequestRateLimit Login rate limit 5/10min
bindRequestRateLimit Bind rate limit 5/10min
SmsIdpConfigFromPasswords(
  // ...
  loginVerificationCodeAllowedAttempts: 5,
  loginRequestRateLimit: SmsRateLimit(
    maxAttempts: 5,
    timeframe: Duration(minutes: 10),
  ),
)

Troubleshooting

Tencent Cloud Rate Limits

Error Code Meaning Solution
LimitExceeded.PhoneNumberOneHourLimit Hourly limit exceeded Wait or adjust in console
LimitExceeded.PhoneNumberDailyLimit Daily limit exceeded Wait until next day
LimitExceeded.PhoneNumberThirtySecondLimit 30-second cooldown Add frontend countdown

Password Regex in Raw Strings

// ❌ Wrong - double escaping
if (!password.contains(RegExp(r'[\\W_]'))) return false;

// ✅ Correct
if (!password.contains(RegExp(r'[\W_]'))) return false;

Async SMS Callback

// ❌ Wrong - async without await causes "Session is closed" error
void _sendSms(Session session, {...}) {
  smsClient.send(...);  // Not awaited
}

// ✅ Correct
Future<void> _sendSms(Session session, {...}) async {
  await smsClient.send(...);
}

Advanced: Individual Package Usage

Not recommended for most users. Only use if you have specific requirements.

If you must use individual packages (e.g., to avoid crypto dependencies when only using hash):

dependencies:
  serverpod_auth_sms_core_server: ^0.1.6
  serverpod_auth_sms_hash_server: ^0.1.6  # Or _crypto_server
// Must manually hide Protocol and Endpoints
import 'package:serverpod_auth_sms_core_server/serverpod_auth_sms_core_server.dart'
    hide Protocol, Endpoints;
import 'package:serverpod_auth_sms_hash_server/serverpod_auth_sms_hash_server.dart'
    hide Protocol, Endpoints;

License

MIT License

Libraries

serverpod_auth_sms