电话号码

电话号码插件

电话号码插件通过允许用户使用电话号码登录和注册,扩展了认证系统。它包含用于验证电话号码的一次性密码(OTP)功能。

安装

将插件添加到服务器

auth.ts
import { betterAuth } from "better-auth"
import { phoneNumber } from "better-auth/plugins"

const auth = betterAuth({
    plugins: [ 
        phoneNumber({  
            sendOTP: ({ phoneNumber, code }, ctx) => { 
                // 实现通过短信发送 OTP 代码
            } 
        }) 
    ] 
})

迁移数据库

运行迁移或生成架构,以向数据库添加必要的字段和表。

npx auth migrate
npx auth generate

可参阅 架构 部分,手动添加字段。

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { phoneNumberClient } from "better-auth/client/plugins"

const authClient = createAuthClient({
    plugins: [
        phoneNumberClient() 
    ]
})

使用

发送 OTP 以进行验证

要向用户的电话号码发送 OTP 以进行验证,可以使用 sendVerificationCode 接口。

POST/phone-number/send-otp
const { data, error } = await authClient.phoneNumber.sendOtp({    phoneNumber: "+1234567890", // required});
Parameters
phoneNumberstringrequired

用于发送 OTP 的电话号码。

验证电话号码

发送 OTP 后,用户可以通过提供验证码来验证其电话号码。

POST/phone-number/verify
const { data, error } = await authClient.phoneNumber.verify({    phoneNumber: "+1234567890", // required    code: "123456", // required    disableSession: false,    updatePhoneNumber: false,});
Parameters
phoneNumberstringrequired

需验证的电话号码。

codestringrequired

OTP 代码。

disableSessionboolean

验证后禁用会话创建。

updatePhoneNumberboolean

更新已登录用户的电话号码。 需要有效的会话。

允许使用电话号码注册

要允许用户使用电话号码注册,可以将 signUpOnVerification 选项传递给插件配置。需要传递 getTempEmail 函数为用户生成临时邮箱。

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

export const auth = betterAuth({
    plugins: [
        phoneNumber({
            sendOTP: ({ phoneNumber, code }, ctx) => {
                // 实现通过短信发送 OTP 代码
            },
            signUpOnVerification: {
                getTempEmail: (phoneNumber) => {
                    return `${phoneNumber}@my-site.com`
                },
                // 可选,您也可以传递 `getTempName` 函数为用户生成临时名称
                getTempName: (phoneNumber) => {
                    return phoneNumber // 默认使用电话号码作为名称
                }
            }
        })
    ]
})

强烈建议不要等待 sendOTP 函数完成。如果等待,将会延迟请求并可能导致时序攻击。对于无服务器平台,可以使用 waitUntil 确保 OTP 被发送。

如果您的用户架构中有额外的必填字段,可以在验证请求体中传递它们:

auth-client.ts
await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890",
    code: "123456",
    customField: "custom-value", // 额外字段
})

使用电话号码登录

除了通过发送-验证流程登录用户,您还可以使用电话号码作为标识符,通过电话号码和密码登录用户。

POST/sign-in/phone-number
const { data, error } = await authClient.signIn.phoneNumber({    phoneNumber: "+1234567890", // required    password, // required    rememberMe: true,});
Parameters
phoneNumberstringrequired

用于登录的电话号码。

passwordstringrequired

用于登录的密码。

rememberMeboolean

记住会话。

更新电话号码

已经登录的用户可以将其电话号码更改为新的号码。首先,向新的电话号码发送 OTP:

import { authClient } from "@/lib/auth-client";

await authClient.phoneNumber.sendOtp({
    phoneNumber: "+1234567890" // 新的电话号码
})

然后使用 updatePhoneNumber: true 验证新的电话号码:

import { authClient } from "@/lib/auth-client";

const isVerified = await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890",
    code: "123456",
    updatePhoneNumber: true
})

禁用会话创建

默认情况下,插件在验证电话号码后会为用户创建会话。您可以通过向 verify 方法传递 disableSession: true 来禁用此行为。

import { authClient } from "@/lib/auth-client";

const isVerified = await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890",
    code: "123456",
    disableSession: true
})

请求密码重置

要使用 phoneNumber 启动密码重置请求流程,可以先调用客户端的 requestPasswordReset 接口向用户的电话号码发送 OTP 代码。

POST/phone-number/request-password-reset
const { data, error } = await authClient.phoneNumber.requestPasswordReset({    phoneNumber: "+1234567890", // required});
Parameters
phoneNumberstringrequired

与用户关联的电话号码。

然后,您可以通过客户端调用 resetPassword 接口,使用 OTP 代码和新密码重置密码。

POST/phone-number/reset-password
const { data, error } = await authClient.phoneNumber.resetPassword({    otp: "123456", // required    phoneNumber: "+1234567890", // required    newPassword: "new-and-secure-password", // required});
Parameters
otpstringrequired

用于重置密码的一次性密码。

phoneNumberstringrequired

需重置密码的账户电话号码。

newPasswordstringrequired

新密码。

选项

otpLength

生成 OTP 代码的长度。默认是 6

sendOTP

一个发送 OTP 代码到用户电话号码的函数。接收电话号码和 OTP 代码作为参数。

expiresIn

OTP 代码过期的时间,单位为秒。默认是 300 秒。

callbackOnVerification

电话号码验证后调用的函数。第一个参数包含电话号码和用户对象,第二个参数是请求对象。

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

export const auth = betterAuth({
    plugins: [
        phoneNumber({
            sendOTP: ({ phoneNumber, code }, ctx) => {
                // 实现通过短信发送 OTP 代码
            },
            callbackOnVerification: async ({ phoneNumber, user }, ctx) => { 
                // 实现电话号码验证后的回调
            } 
        })
    ]
})

sendPasswordResetOTP

发送 OTP 代码到用户电话号码用于密码重置的函数。接收电话号码和 OTP 代码作为参数。

phoneNumberValidator

自定义电话号码验证函数。接收电话号码为参数,返回布尔值表示电话号码是否有效。

verifyOTP

自定义 OTP 验证函数。提供后将替代默认内部验证逻辑。适用于需集成外部短信服务提供商(如 Twilio Verify、AWS SNS)进行 OTP 验证的场景。函数接收带有 phoneNumbercode 属性的对象及请求对象,返回布尔值或返回布尔值的 Promise,表示 OTP 是否有效。

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

export const auth = betterAuth({
    plugins: [
        phoneNumber({
            sendOTP: ({ phoneNumber, code }, ctx) => {
                // 通过您的短信服务提供商发送 OTP
            },
            verifyOTP: async ({ phoneNumber, code }, ctx) => { 
                // 使用您选择的方式验证 OTP(例如 Twilio Verify)
                // 以下为示例,非真实实现。
                const isValid = await twilioClient.verify 
                    .services('YOUR_SERVICE_SID') 
                    .verificationChecks 
                    .create({ to: phoneNumber, code }); 
                return isValid.status === 'approved'; 
            } 
        })
    ]
})

使用此选项时,请确保实现了适当的验证,因为这将覆盖内部验证逻辑。

signUpOnVerification

一个包含以下属性的对象:

  • getTempEmail: 用于为用户生成临时邮箱的函数,接收电话号码为参数,返回临时邮箱字符串。
  • getTempName: 用于为用户生成临时名称的函数,接收电话号码为参数,返回临时名称字符串。

requireVerification

启用后,用户在验证电话号码前无法使用该电话号码登录。如果未验证用户尝试登录,服务器将返回 401 错误(PHONE_NUMBER_NOT_VERIFIED),并自动触发 OTP 发送,启动验证流程。

架构

本插件需要向用户表中添加2个字段

用户表

Table
字段
类型
描述
phoneNumber
string
?
用户的电话号码
phoneNumberVerified
boolean
?
电话号码是否已验证

OTP 验证尝试次数限制

电话号码插件内置防暴力攻击保护,限制每个 OTP 代码的验证尝试次数。

phoneNumber({
  allowedAttempts: 3, // 默认是3
  // ... 其它选项
})

当用户超过允许的验证尝试次数时:

  • OTP 代码会自动删除
  • 进一步的验证尝试会返回 403(禁止访问)状态及“尝试次数过多”提示
  • 用户需重新请求新的 OTP 代码以继续操作

超出尝试次数后的示例错误响应:

{
  "error": {
    "status": 403,
    "message": "尝试次数过多"
  }
}

接收到 403 状态码时,请提示用户请求新的 OTP 代码