文档

手机短信登录 & 绑定功能说明

手机短信登录 & 绑定功能说明

功能概述

本系统使用 Better Auth 原生 phoneNumber 插件 实现完整的手机短信验证功能,支持手机号登录和绑定两种场景。经过重构,我们现在采用标准化的实现方式,获得更好的安全性和可维护性。

架构决策

为什么选择 Better Auth 原生实现

Better Auth phoneNumber 插件的优势

  • 原生会话管理:完美兼容 Better Auth 会话系统,无兼容性问题
  • 内置安全特性:防暴力攻击、验证码过期管理、尝试次数限制
  • 支持多种场景:登录、绑定、密码重置等完整流程
  • 标准化实现:遵循 OAuth 和认证标准,减少维护负担
  • 灵活配置:支持自定义验证器、过期时间、回调函数等

支持的复杂业务场景

  • 手机号直接登录:通过 disableSession: false 创建会话
  • 已登录用户绑定手机号:通过 updatePhoneNumber: true + disableSession: true
  • 微信登录后绑定流程:保持现有会话,更新手机号信息
  • 完整的状态查询:通过 Better Auth session API 获取用户状态

API架构

Better Auth 原生端点

手机号验证功能通过 Better Auth 的标准端点提供:

1. 发送验证码

POST /api/auth/phone-number/send-otp
Content-Type: application/json

{
  "phoneNumber": "+8613800138000"
}

2. 验证验证码

POST /api/auth/phone-number/verify
Content-Type: application/json

{
  "phoneNumber": "+8613800138000",
  "code": "123456",
  "updatePhoneNumber": false,  // true: 绑定模式, false: 登录模式
  "disableSession": false      // true: 不创建会话, false: 创建会话
}

统一API客户端

位置:/src/lib/auth/phone-api.ts

这是对 Better Auth 客户端的封装,保持向后兼容:

// 发送验证码 - 兼容原有接口
export async function sendPhoneOTP(
  phoneNumber: string,
  type: "LOGIN" | "REGISTRATION" = "LOGIN"
): Promise<{ error?: { message: string } }>

// 验证验证码 - 兼容原有接口  
export async function verifyPhoneOTP(
  phoneNumber: string,
  code: string,
  type: "LOGIN" | "REGISTRATION" = "LOGIN",
  updatePhoneNumber = false
): Promise<{ data?: any; error?: { message: string } }>

// 获取验证状态 - 使用 Better Auth session
export async function getPhoneVerificationStatus()

内部实现映射:

  • LOGIN 模式 → updatePhoneNumber: false, disableSession: false
  • REGISTRATION 模式 → updatePhoneNumber: true, disableSession: true

服务端配置

Better Auth 插件配置

src/lib/auth/auth.ts 中的配置:

import { phoneNumber } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    // ... 其他插件
    phoneNumber({
      // SMS 发送集成腾讯云
      sendOTP: async ({ phoneNumber, code }, request) => {
        const { sendVerificationCodeSMS } = await import("@/lib/sms/tencent-sms");
        const result = await sendVerificationCodeSMS(phoneNumber, code);
        if (!result.success) {
          throw new Error(result.message || "发送验证码失败");
        }
      },
      
      // 密码重置 OTP 发送
      sendPasswordResetOTP: async ({ phoneNumber, code }, request) => {
        const { sendVerificationCodeSMS } = await import("@/lib/sms/tencent-sms");
        const result = await sendVerificationCodeSMS(phoneNumber, code);
        if (!result.success) {
          throw new Error(result.message || "发送验证码失败");
        }
      },
      
      // 支持手机号登录时自动注册
      signUpOnVerification: {
        getTempEmail: (phoneNumber) => `${phoneNumber.replace("+", "")}@sms.hackathonweekly.com`,
        getTempName: (phoneNumber) => `用户${phoneNumber.slice(-4)}`,
      },
      
      // 手机号格式验证
      phoneNumberValidator: async (phoneNumber) => {
        const { validateFullPhoneNumber } = await import("@/lib/utils/phone-validation");
        const validation = validateFullPhoneNumber(phoneNumber);
        return validation.isValid;
      },
      
      // 安全配置
      otpLength: 6,           // 6位验证码
      expiresIn: 300,         // 5分钟过期
      allowedAttempts: 3,     // 最多3次尝试
      
      // 验证成功回调
      callbackOnVerification: async ({ phoneNumber, user }, request) => {
        logger.info(`Phone number ${phoneNumber} verified for user ${user.id}`);
      },
    }),
  ],
});

客户端配置

src/lib/auth/client.ts 中:

import { phoneNumberClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    // ... 其他插件
    phoneNumberClient(),  // 已经配置
  ],
});

业务场景支持

1. 手机号注册/登录

// 发送验证码
await sendPhoneOTP("+8613800138000", "LOGIN");

// 验证码验证 - 自动创建用户和会话
await verifyPhoneOTP("+8613800138000", "123456", "LOGIN");

2. 微信登录后绑定手机号

// 用户已通过微信登录,现在绑定手机号
await sendPhoneOTP("+8613800138000", "REGISTRATION");

// 验证成功后绑定到当前用户,不创建新会话
await verifyPhoneOTP("+8613800138000", "123456", "REGISTRATION");

3. 设置页面手机号管理

// 直接使用 Better Auth 客户端
await authClient.phoneNumber.sendOtp({ phoneNumber: "+8613800138000" });
await authClient.phoneNumber.verify({ 
  phoneNumber: "+8613800138000", 
  code: "123456",
  updatePhoneNumber: true,
  disableSession: true 
});

配置选项

手机号验证配置

src/config/index.ts 中提供了完整的手机号验证控制选项:

export const config = {
  auth: {
    // === 手机号验证设置 ===
    
    // 是否强制用户验证手机号(默认不强制)
    // 当为 true 时,通过 OAuth(微信等)登录的用户必须绑定手机号
    requirePhoneVerification: false,
    
    // 是否允许用户跳过手机号验证(仅在 requirePhoneVerification 为 true 时生效)
    // 当为 true 时,会显示"暂时跳过"选项
    allowSkipPhoneVerification: true,
    
    // 是否在设置页面显示手机号绑定选项(即使未强制要求)
    // 当为 false 时,将完全隐藏手机号绑定功能
    enablePhoneBinding: true,
    
    // 其他认证配置...
  }
}

这些配置选项的工作方式与之前相同,但现在通过 Better Auth 的原生机制实现。

数据库模型

Better Auth phoneNumber 插件会自动添加以下字段到 User 表:

model User {
  phoneNumber        String? @unique
  phoneNumberVerified Boolean? @default(false)
  // 其他字段由 Better Auth 自动管理...
}

验证码信息存储在 Better Auth 的内部表中,无需手动管理。

安全特性

内置安全保护

  • 防暴力攻击:最多3次验证尝试,超出后需重新获取验证码
  • 验证码管理:6位数字,5分钟过期,使用后自动删除
  • 会话安全:原生 Better Auth 会话管理,支持 CSRF 保护等
  • 速率限制:通过腾讯云 SMS 服务的内置限制

Better Auth 安全优势

相比自定义实现,Better Auth 提供:

  • 标准化的会话管理和 Cookie 设置
  • 内置的 CSRF 保护
  • 安全的密码哈希和会话令牌生成
  • 规范的错误处理和状态管理

技术集成

腾讯云SMS服务

SMS 发送功能通过 Better Auth 插件配置集成:

# .env 配置
TENCENT_SMS_SECRET_ID=your_secret_id
TENCENT_SMS_SECRET_KEY=your_secret_key
TENCENT_SMS_REGION=ap-guangzhou
TENCENT_SMS_SDK_APP_ID=your_app_id
TENCENT_SMS_SIGN_NAME=your_sign_name
TENCENT_SMS_TEMPLATE_ID=your_template_id

⚠️ 腾讯云短信服务目前只支持 ap-guangzhou 区域,若使用其他区域会返回 “The action not support this region” 错误。

API 端点

所有手机号相关的 API 现在通过 Better Auth 统一处理:

  • POST /api/auth/phone-number/send-otp - 发送验证码
  • POST /api/auth/phone-number/verify - 验证验证码
  • POST /api/auth/phone-number/request-password-reset - 请求密码重置
  • POST /api/auth/phone-number/reset-password - 重置密码

迁移说明

从自定义实现迁移

如果你之前使用的是自定义手机验证实现,迁移步骤:

  1. 保持前端兼容phone-api.ts 保持了相同的接口
  2. 删除自定义路由:不再需要 /api/auth/phone/* 路由
  3. 数据库兼容:Better Auth 使用相同的 User 表字段
  4. 会话自动迁移:Better Auth 会话与原有系统兼容

主要改进

  • 会话创建问题解决:不再有会话兼容性问题
  • 代码量减少:删除了约300行自定义代码
  • 标准化:遵循认证行业标准
  • 维护性提升:利用 Better Auth 的活跃维护

开发指南

添加新的验证场景

// 直接使用 Better Auth 客户端API
await authClient.phoneNumber.sendOtp({ phoneNumber });
await authClient.phoneNumber.verify({ 
  phoneNumber, 
  code,
  updatePhoneNumber: boolean,
  disableSession: boolean 
});

集成其他SMS服务

phoneNumber 插件配置中修改 sendOTP 函数:

phoneNumber({
  sendOTP: async ({ phoneNumber, code }, request) => {
    // 替换为其他SMS服务
    await yourSMSProvider.send(phoneNumber, code);
  },
})

测试建议

  • 测试 Better Auth 原生端点:/api/auth/phone-number/*
  • 测试会话创建和管理
  • 测试不同验证场景的参数组合
  • 测试安全特性(尝试次数限制等)

未来优化方向

  1. 更细粒度的配置:利用 Better Auth 插件的更多配置选项
  2. 多因素认证:结合 Better Auth 的 twoFactor 插件
  3. 国际化SMS:扩展支持更多 SMS 服务提供商
  4. 监控集成:利用 Better Auth 的内置日志和监控

On this page

No Headings