# 电话号码 (/docs/plugins/phone-number)

电话号码插件



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

## 安装 [#安装]

<Steps>
  <Step>
    ### 将插件添加到服务器 [#将插件添加到服务器]

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth"
    import { phoneNumber } from "better-auth/plugins" // [!code highlight]

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

  <Step>
    ### 迁移数据库 [#迁移数据库]

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

    <Tabs items="[&#x22;migrate&#x22;, &#x22;generate&#x22;]">
      <Tab value="migrate">
        <CodeBlockTabs defaultValue="npm" groupId="persist-install">
          <CodeBlockTabsList>
            <CodeBlockTabsTrigger value="npm">
              npm
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="pnpm">
              pnpm
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="yarn">
              yarn
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="bun">
              bun
            </CodeBlockTabsTrigger>
          </CodeBlockTabsList>

          <CodeBlockTab value="npm">
            ```bash
            npx auth migrate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="pnpm">
            ```bash
            pnpm dlx auth migrate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="yarn">
            ```bash
            yarn dlx auth migrate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="bun">
            ```bash
            bun x auth migrate
            ```
          </CodeBlockTab>
        </CodeBlockTabs>
      </Tab>

      <Tab value="generate">
        <CodeBlockTabs defaultValue="npm" groupId="persist-install">
          <CodeBlockTabsList>
            <CodeBlockTabsTrigger value="npm">
              npm
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="pnpm">
              pnpm
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="yarn">
              yarn
            </CodeBlockTabsTrigger>

            <CodeBlockTabsTrigger value="bun">
              bun
            </CodeBlockTabsTrigger>
          </CodeBlockTabsList>

          <CodeBlockTab value="npm">
            ```bash
            npx auth generate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="pnpm">
            ```bash
            pnpm dlx auth generate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="yarn">
            ```bash
            yarn dlx auth generate
            ```
          </CodeBlockTab>

          <CodeBlockTab value="bun">
            ```bash
            bun x auth generate
            ```
          </CodeBlockTab>
        </CodeBlockTabs>
      </Tab>
    </Tabs>

    请参阅 [Schema](#schema) 部分以手动添加字段。
  </Step>

  <Step>
    ### 添加客户端插件 [#添加客户端插件]

    ```ts title="auth-client.ts"
    import { createAuthClient } from "better-auth/client"
    import { phoneNumberClient } from "better-auth/client/plugins" // [!code highlight]

    const authClient = createAuthClient({
        plugins: [
            phoneNumberClient() // [!code highlight]
        ]
    })
    ```
  </Step>
</Steps>

## 使用 [#使用]

### 发送 OTP 以进行验证 [#发送-otp-以进行验证]

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

**Endpoint:** `POST /phone-number/send-otp`

### Client Side

```ts
const { data, error } = await authClient.phoneNumber.sendOtp({
    phoneNumber: "+1234567890", // required, 要发送 OTP 的电话号码。
});
```

### Server Side

```ts
const data = await auth.api.sendPhoneNumberOTP({
    body: {
        phoneNumber: "+1234567890", // required, 要发送 OTP 的电话号码。
    },
});
```

### Type Definition

```ts
type sendPhoneNumberOTP = {
    /**
     * 要发送 OTP 的电话号码。 
     */
    phoneNumber: string = "+1234567890"
}
```

### 验证电话号码 [#验证电话号码]

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

**Endpoint:** `POST /phone-number/verify`

### Client Side

```ts
const { data, error } = await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890", // required, 要验证的电话号码。
    code: "123456", // required, OTP 验证码。
    disableSession: false, // 验证后是否禁用会话创建。
    updatePhoneNumber: false, // 更新现有登录用户的电话号码。 需要活跃的会话。
});
```

### Server Side

```ts
const data = await auth.api.verifyPhoneNumber({
    body: {
        phoneNumber: "+1234567890", // required, 要验证的电话号码。
        code: "123456", // required, OTP 验证码。
        disableSession: false, // 验证后是否禁用会话创建。
        updatePhoneNumber: false, // 更新现有登录用户的电话号码。 需要活跃的会话。
    },
});
```

### Type Definition

```ts
type verifyPhoneNumber = {
    /**
     * 要验证的电话号码。
     */
    phoneNumber: string = "+1234567890"
    /**
     * OTP 验证码。
     */
    code: string = "123456"
    /**
     * 验证后是否禁用会话创建。
     */
    disableSession?: boolean = false
    /**
     * 更新现有登录用户的电话号码。
     * 需要活跃的会话。
     */
    updatePhoneNumber?: boolean = false
}
```

### 在服务器上使用 OTP [#在服务器上使用-otp]

对于自定义注册或账户关联流程，可以使用 OTP，而无需创建或更新用户或会话。此仅限服务器端的 API 通过 `auth.api` 暴露；它不会注册 HTTP 路由，也不会生成 `authClient` 方法。

```ts title="auth.ts"
const result = await auth.api.consumePhoneNumberOTP({
    body: {
        phoneNumber: "+1234567890",
        code: "123456"
    }
})
```

此方法使用与 `verifyPhoneNumber` 相同的验证和使用行为；它不会引入更强的并发保证。如果您的服务器流程可能会收到并行兑换尝试，并且需要严格保证单次使用，请使用以原子方式使用已接受代码的提供商配置 [`verifyOTP`](#verifyotp)。

请在使用验证结果的服务器端流程中调用此方法；它不会返回会话或可重复使用的验证证明。请先验证不依赖于 OTP 验证成功的任何输入。如果您通过自己的公共端点暴露此功能，请在那里应用速率限制和适当的滥用防护。

### 允许使用电话号码注册 [#允许使用电话号码注册]

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

```ts title="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 // 默认使用电话号码作为名称
                }
            }
        })
    ]
})
```

<Callout>
  我们强烈建议不要等待 `sendOTP` 函数。如果等待它，会减慢请求速度，并可能导致定时攻击。对于无服务器平台，可以使用 `waitUntil` 来确保 OTP 已发送。
</Callout>

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

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

### 使用电话号码登录 [#使用电话号码登录]

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

<Callout type="warn">
  要使用电话号码和密码登录，用户必须在 `account` 表中具有对应的记录，并且 `providerId` 必须设置为 `"credential"`。如果您是从其他身份验证提供商迁移或手动播种用户，请确保此记录存在。
</Callout>

**Endpoint:** `POST /sign-in/phone-number`

### Client Side

```ts
const { data, error } = await authClient.signIn.phoneNumber({
    phoneNumber: "+1234567890", // required, 要登录的电话号码。
    password, // required, 用于登录的密码。
    rememberMe: true, // 记住会话。
});
```

### Server Side

```ts
const data = await auth.api.signInPhoneNumber({
    body: {
        phoneNumber: "+1234567890", // required, 要登录的电话号码。
        password, // required, 用于登录的密码。
        rememberMe: true, // 记住会话。
    },
});
```

### Type Definition

```ts
type signInPhoneNumber = {
    /**
     * 要登录的电话号码。 
     */
    phoneNumber: string = "+1234567890"
    /**
     * 用于登录的密码。 
     */
    password: string
    /**
     * 记住会话。 
     */
    rememberMe?: boolean = true
}
```

### 更新电话号码 [#更新电话号码]

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

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

await authClient.phoneNumber.sendOtp({
    phoneNumber: "+1234567890" // 新的电话号码 // [!code highlight]
})
```

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

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

const isVerified = await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890",
    code: "123456",
    updatePhoneNumber: true // [!code highlight]
})
```

### 移除电话号码 [#移除电话号码]

已登录用户可以通过向 `updateUser` 传递 `null` 来释放其电话号码。插件会原子性地清除电话号码并重置已验证标志，从而释放该号码，使其他账号可以通过标准验证流程来认领它。

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

await authClient.updateUser({
    phoneNumber: null // [!code highlight]
})
```

出于安全原因，通过 `updateUser` 更新非空电话号码仍然被阻止。将电话号码更改为不同号码始终需要通过 `verify` 并设置 `updatePhoneNumber: true` 进行 OTP 验证。

### 禁用会话创建 [#禁用会话创建]

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

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

const isVerified = await authClient.phoneNumber.verify({
    phoneNumber: "+1234567890",
    code: "123456",
    disableSession: true // [!code highlight]
})
```

### 请求密码重置 [#请求密码重置]

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

**Endpoint:** `POST /phone-number/request-password-reset`

### Client Side

```ts
const { data, error } = await authClient.phoneNumber.requestPasswordReset({
    phoneNumber: "+1234567890", // required, 与用户关联的电话号码。
});
```

### Server Side

```ts
const data = await auth.api.requestPasswordResetPhoneNumber({
    body: {
        phoneNumber: "+1234567890", // required, 与用户关联的电话号码。
    },
});
```

### Type Definition

```ts
type requestPasswordResetPhoneNumber = {
    /**
     * 与用户关联的电话号码。 
     */
    phoneNumber: string = "+1234567890"
}
```

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

**Endpoint:** `POST /phone-number/reset-password`

### Client Side

```ts
const { data, error } = await authClient.phoneNumber.resetPassword({
    otp: "123456", // required, 用于重置密码的一次性密码。
    phoneNumber: "+1234567890", // required, 要重置密码的账户电话号码。
    newPassword: "new-and-secure-password", // required, 新密码。
});
```

### Server Side

```ts
const data = await auth.api.resetPasswordPhoneNumber({
    body: {
        otp: "123456", // required, 用于重置密码的一次性密码。
        phoneNumber: "+1234567890", // required, 要重置密码的账户电话号码。
        newPassword: "new-and-secure-password", // required, 新密码。
    },
});
```

### Type Definition

```ts
type resetPasswordPhoneNumber = {
    /**
     * 用于重置密码的一次性密码。 
     */
    otp: string = "123456"
    /**
     * 要重置密码的账户电话号码。 
     */
    phoneNumber: string = "+1234567890"
    /**
     * 新密码。 
     */
    newPassword: string = "new-and-secure-password"
}
```

如果新密码被拒绝（例如被 [Have I Been Pwned](/docs/plugins/have-i-been-pwned) 插件拒绝），OTP 不会被消耗，可以再次使用。如果重置后续步骤失败，例如撤销会话或保存密码，只有在数据库适配器启用了事务且验证值存储在数据库中时，OTP 才仍可使用。未配置二级存储时默认如此；使用[二级存储](/docs/concepts/database#secondary-storage)时，请设置 [`verification.storeInDatabase: true`](/docs/reference/options#verification)。错误的 OTP 仍会计入 `allowedAttempts`。

使用自定义 [`verifyOTP`](#verifyotp) 时，验证码由你的提供方检查且通常会被消耗，因此不适用上述保证。过短或过长的密码会在调用提供方之前被拒绝；但之后被拒绝的密码（例如被 Have I Been Pwned 拒绝）已经消耗验证码，用户需要重新申请。

## Options [#options]

### `otpLength` [#otplength]

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

### `sendOTP` [#sendotp]

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

### `expiresIn` [#expiresin]

OTP 代码的过期时间（以秒为单位）。默认为 `300` 秒。

### `callbackOnVerification` [#callbackonverification]

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

```ts
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) => { // [!code highlight]
                // 实现电话号码验证后的回调 // [!code highlight]
            } // [!code highlight]
        })
    ]
})
```

### `sendPasswordResetOTP` [#sendpasswordresetotp]

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

### `phoneNumberValidator` [#phonenumbervalidator]

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

### `verifyOTP` [#verifyotp]

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

The function checks the codes for phone number verification, [`consumePhoneNumberOTP`](#consume-an-otp-on-the-server), and [password reset](#request-password-reset). If `sendPasswordResetOTP` sends the code Better Auth generates rather than a code from your provider, `verifyOTP` must accept that code too.

```ts
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) => { // [!code highlight]
                // 使用您选择的方式验证 OTP（例如 Twilio Verify） // [!code highlight]
                // 以下为示例，非真实实现。 // [!code highlight]
                const isValid = await twilioClient.verify // [!code highlight]
                    .services('YOUR_SERVICE_SID') // [!code highlight]
                    .verificationChecks // [!code highlight]
                    .create({ to: phoneNumber, code }); // [!code highlight]
                return isValid.status === 'approved'; // [!code highlight]
            } // [!code highlight]
        })
    ]
})
```

<Callout type="warn">
  使用此选项时，请确保实现了适当的验证，因为会覆盖默认的内部验证逻辑。
</Callout>

### `signUpOnVerification` [#signuponverification]

包含以下属性的对象：

* `getTempEmail`: 一个生成用户临时邮箱的函数。接收电话号码作为参数并返回临时邮箱。
* `getTempName`: 一个生成用户临时名称的函数。接收电话号码作为参数并返回临时名称。

### `requireVerification` [#requireverification]

启用后，用户在电话号码验证之前无法登录。如果未验证的电话号码尝试登录，服务器将返回 401 错误（PHONE\_NUMBER\_NOT\_VERIFIED）并自动触发 OTP 发送以开始验证过程。

## 架构 [#架构]

该插件需要在用户表中添加 2 个字段

### 用户表。 [#用户表]



<DatabaseTable name="user" fields="phoneNumberUserTableFields" />

### OTP 验证尝试次数限制 [#otp-验证尝试次数限制]

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

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

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

* OTP 代码将被自动删除
* 进一步的验证尝试将返回 403（禁止）状态，附带“尝试次数过多”消息
* 用户需要请求新的 OTP 代码才能继续

超过尝试次数后的示例错误响应：

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

<Callout type="warning">
  收到 403 状态时，请提示用户请求新的 OTP 代码
</Callout>

