# 电子邮件 OTP (/docs/plugins/email-otp)

Better Auth 的电子邮件 OTP 插件。



电子邮件 OTP 插件允许用户通过发送到其电子邮件地址的一次性密码（OTP）进行登录、验证电子邮件或重置密码。

## 安装 [#安装]

<Steps>
  <Step>
    ### 将插件添加到您的身份验证配置中 [#将插件添加到您的身份验证配置中]

    将 `emailOTP` 插件添加到您的身份验证配置，并实现 `sendVerificationOTP()` 方法。

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

    export const auth = betterAuth({
        // ... 其他配置选项
        plugins: [
            emailOTP({ // [!code highlight]
                async sendVerificationOTP({ email, otp, type }) { // [!code highlight]
                    if (type === "sign-in") { // [!code highlight]
                        // 发送登录的 OTP // [!code highlight]
                    } else if (type === "email-verification") { // [!code highlight]
                        // 发送电子邮件验证的 OTP // [!code highlight]
                    } else { // [!code highlight]
                        // 发送密码重置的 OTP // [!code highlight]
                    } // [!code highlight]
                }, // [!code highlight]
            }) // [!code highlight]
        ]
    })
    ```
  </Step>

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

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

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

## 使用方法 [#使用方法]

### 发送 OTP [#发送-otp]

使用 `sendVerificationOtp()` 方法向用户的电子邮件地址发送 OTP。

**Endpoint:** `POST /email-otp/send-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "sign-in", // required, OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。
});
```

### Server Side

```ts
const data = await auth.api.sendVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "sign-in", // required, OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。
    },
});
```

### Type Definition

```ts
type sendVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。 
     */
    type: "email-verification" | "sign-in" | "forget-password" = "sign-in"
}
```

### 校验 OTP（可选） [#校验-otp可选]

使用 `checkVerificationOtp()` 方法验证 OTP 是否有效。

**Endpoint:** `POST /email-otp/check-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.checkVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "sign-in", // required, OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。
    otp: "123456", // required, 发送到电子邮件的 OTP。
});
```

### Server Side

```ts
const data = await auth.api.checkVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "sign-in", // required, OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。
        otp: "123456", // required, 发送到电子邮件的 OTP。
    },
});
```

### Type Definition

```ts
type checkVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。`sign-in`、`email-verification` 或 `forget-password`。 
     */
    type: "email-verification" | "sign-in" | "forget-password" = "sign-in"
    /**
     * 发送到电子邮件的 OTP。 
     */
    otp: string = "123456"
}
```

### 使用 OTP 登录 [#使用-otp-登录]

使用 OTP 登录时，先通过 `sendVerificationOtp()` 方法向用户电子邮件发送类型为 `"sign-in"` 的 OTP。

**Endpoint:** `POST /email-otp/send-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "sign-in", // required, OTP 的类型。
});
```

### Server Side

```ts
const data = await auth.api.sendVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "sign-in", // required, OTP 的类型。
    },
});
```

### Type Definition

```ts
type sendVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。
     */
    type: "sign-in" = "sign-in"
}
```

用户提供 OTP 后，可使用 `signIn.emailOtp()` 方法登录用户。

**Endpoint:** `POST /sign-in/email-otp`

### Client Side

```ts
const { data, error } = await authClient.signIn.emailOtp({
    email: "user@example.com", // required, 登录所用的电子邮件地址。
    otp: "123456", // required, 发送到电子邮件的 OTP。
    name: "John Doe", // 用户显示名称。仅在用户首次注册时使用。
    image: "https://example.com/image.png", // 用户头像 URL。仅在用户首次注册时使用。
});
```

### Server Side

```ts
const data = await auth.api.signInEmailOTP({
    body: {
        email: "user@example.com", // required, 登录所用的电子邮件地址。
        otp: "123456", // required, 发送到电子邮件的 OTP。
        name: "John Doe", // 用户显示名称。仅在用户首次注册时使用。
        image: "https://example.com/image.png", // 用户头像 URL。仅在用户首次注册时使用。
    },
});
```

### Type Definition

```ts
type signInEmailOTP = {
    /**
     * 登录所用的电子邮件地址。
     */
    email: string = "user@example.com"
    /**
     * 发送到电子邮件的 OTP。
     */
    otp: string = "123456"
    /**
     * 用户显示名称。仅在用户首次注册时使用。
     */
    name?: string = "John Doe"
    /**
     * 用户头像 URL。仅在用户首次注册时使用。
     */
    image?: string = "https://example.com/image.png"
}
```

<Callout>
  如果用户未注册，他们将自动注册。为新用户也会接受已配置的[附加字段](/docs/concepts/typescript#额外字段)。若要阻止自动注册，请在[选项](#选项)中将 `disableSignUp` 设为 `true`。
</Callout>

<Callout>
  当签名 OTP 确认了一个已有账户且该账户的电子邮件从未验证过时，该账户上任何现有密码都会被移除，其会话也会被撤销。用户会通过 OTP 登录，并可通过密码重置设置新密码。这样可确保由 OTP 证明的邮箱所有权作为该账户的事实来源。
</Callout>

### 使用 OTP 验证电子邮件 [#使用-otp-验证电子邮件]

使用 OTP 验证用户邮箱时，先通过 `sendVerificationOtp()` 方法向用户邮箱发送类型为 `"email-verification"` 的 OTP。

**Endpoint:** `POST /email-otp/send-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "email-verification", // required, OTP 的类型。
});
```

### Server Side

```ts
const data = await auth.api.sendVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "email-verification", // required, OTP 的类型。
    },
});
```

### Type Definition

```ts
type sendVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。
     */
    type: "email-verification" = "email-verification"
}
```

用户输入 OTP 后，使用 `verifyEmail()` 方法完成邮箱验证。

**Endpoint:** `POST /email-otp/verify-email`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.verifyEmail({
    email: "user@example.com", // required, 要验证的电子邮件地址。
    otp: "123456", // required, 用于验证的 OTP。
});
```

### Server Side

```ts
const data = await auth.api.verifyEmailOTP({
    body: {
        email: "user@example.com", // required, 要验证的电子邮件地址。
        otp: "123456", // required, 用于验证的 OTP。
    },
});
```

### Type Definition

```ts
type verifyEmailOTP = {
    /**
     * 要验证的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * 用于验证的 OTP。 
     */
    otp: string = "123456"
}
```

### 使用 OTP 重置密码 [#使用-otp-重置密码]

使用 OTP 重置密码时，使用 `emailOtp.requestPasswordReset()` 方法向用户邮箱发送类型为 `"forget-password"` 的 OTP。

**Endpoint:** `POST /email-otp/request-password-reset`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.requestPasswordReset({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
});
```

### Server Side

```ts
const data = await auth.api.requestPasswordResetEmailOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    },
});
```

### Type Definition

```ts
type requestPasswordResetEmailOTP = {
    /**
     * 发送 OTP 的电子邮件地址。
     */
    email: string = "user@example.com"
}
```

<Callout type="warn">
  `/forget-password/email-otp` 端点已弃用。请改用 `/email-otp/request-password-reset`。
</Callout>

用户提供 OTP 后，可以选择使用 `checkVerificationOtp()` 方法验证 OTP 是否有效。

**Endpoint:** `POST /email-otp/check-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.checkVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "forget-password", // required, OTP 的类型。
    otp: "123456", // required, 发送到电子邮件的 OTP。
});
```

### Server Side

```ts
const data = await auth.api.checkVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "forget-password", // required, OTP 的类型。
        otp: "123456", // required, 发送到电子邮件的 OTP。
    },
});
```

### Type Definition

```ts
type checkVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。
     */
    type: "forget-password" = "forget-password"
    /**
     * 发送到电子邮件的 OTP。 
     */
    otp: string = "123456"
}
```

然后，使用 `resetPassword()` 方法重置用户密码。

**Endpoint:** `POST /email-otp/reset-password`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.resetPassword({
    email: "user@example.com", // required, 用于重置密码的电子邮件地址。
    otp: "123456", // required, 发送到电子邮件的 OTP。
    password: "new-secure-password", // required, 新密码。
});
```

### Server Side

```ts
const data = await auth.api.resetPasswordEmailOTP({
    body: {
        email: "user@example.com", // required, 用于重置密码的电子邮件地址。
        otp: "123456", // required, 发送到电子邮件的 OTP。
        password: "new-secure-password", // required, 新密码。
    },
});
```

### Type Definition

```ts
type resetPasswordEmailOTP = {
    /**
     * 用于重置密码的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * 发送到电子邮件的 OTP。 
     */
    otp: string = "123456"
    /**
     * 新密码。 
     */
    password: string = "new-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`](#options)。

### 使用 OTP 更改电子邮件 [#使用-otp-更改电子邮件]

要允许用户使用 OTP 更改电子邮件，需先启用 `changeEmail` 功能，该功能默认关闭。将 `changeEmail.enabled` 设置为 `true`：

```ts title="auth.ts"
import { betterAuth } from "better-auth";

export const auth = betterAuth({
    plugins: [
        emailOTP({
            changeEmail: {
                enabled: true, // [!code highlight]
            }
        })
    ]
})
```

默认情况下，当用户请求更改电子邮件时，OTP 会发送到**新的**电子邮件地址。只有用户验证了新邮箱后，邮箱才会被更新。

#### 使用方法 [#使用方法-1]

要更改用户电子邮件地址，使用 `emailOtp.requestEmailChange()` 方法向用户新的电子邮件地址发送类型为 `"change-email"` 的 OTP。

**Endpoint:** `POST /email-otp/request-email-change`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.requestEmailChange({
    newEmail: "user@example.com", // required, 要发送 OTP 的新电子邮件地址。
    otp: "123456", // 发送到当前邮箱的 OTP。 当 `changeEmail.verifyCurrentEmail` 选项设置为 `true` 时必需。
});
```

### Server Side

```ts
const data = await auth.api.requestEmailChangeEmailOTP({
    body: {
        newEmail: "user@example.com", // required, 要发送 OTP 的新电子邮件地址。
        otp: "123456", // 发送到当前邮箱的 OTP。 当 `changeEmail.verifyCurrentEmail` 选项设置为 `true` 时必需。
    },
    // This endpoint requires session cookies.
    headers: await headers(),
});
```

### Type Definition

```ts
type requestEmailChangeEmailOTP = {
    /**
     * 要发送 OTP 的新电子邮件地址。
     */
    newEmail: string = "user@example.com"
    /**
     * 发送到当前邮箱的 OTP。 当 `changeEmail.verifyCurrentEmail` 选项设置为 `true` 时必需。
     */
    otp?: string = "123456"
}
```

用户提供 OTP 后，使用 `changeEmail()` 方法更改用户邮箱地址。

**Endpoint:** `POST /email-otp/change-email`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.changeEmail({
    newEmail: "user@example.com", // required, 要更改为的新电子邮件地址。
    otp: "123456", // required, 发送到新邮箱的 OTP。
});
```

### Server Side

```ts
const data = await auth.api.changeEmailEmailOTP({
    body: {
        newEmail: "user@example.com", // required, 要更改为的新电子邮件地址。
        otp: "123456", // required, 发送到新邮箱的 OTP。
    },
    // This endpoint requires session cookies.
    headers: await headers(),
});
```

### Type Definition

```ts
type changeEmailEmailOTP = {
    /**
     * 要更改为的新电子邮件地址。 
     */
    newEmail: string = "user@example.com"
    /**
     * 发送到新邮箱的 OTP。 
     */
    otp: string = "123456"
}
```

#### 使用当前邮箱确认 [#使用当前邮箱确认]

为了增加安全性，您可以要求用户先通过发送到**当前**邮箱的 OTP 进行确认，再发送 OTP 到新邮箱。要启用此功能，请在插件选项中将 `changeEmail.verifyCurrentEmail` 设置为 `true`。

```ts title="auth.ts"
import { betterAuth } from "better-auth";

export const auth = betterAuth({
    plugins: [
        emailOTP({
            changeEmail: {
                enabled: true,
                verifyCurrentEmail: true, // [!code highlight]
            }
        })
    ]
})
```

在请求邮箱更改前，使用类型为 `email-verification` 的 `sendVerificationOtp()` 方法向用户邮箱发送 OTP。

**Endpoint:** `POST /email-otp/send-verification-otp`

### Client Side

```ts
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
    email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
    type: "email-verification", // required, OTP 的类型。必须为 `email-verification`，用于确认邮箱更改。
});
```

### Server Side

```ts
const data = await auth.api.sendVerificationOTP({
    body: {
        email: "user@example.com", // required, 发送 OTP 的电子邮件地址。
        type: "email-verification", // required, OTP 的类型。必须为 `email-verification`，用于确认邮箱更改。
    },
});
```

### Type Definition

```ts
type sendVerificationOTP = {
    /**
     * 发送 OTP 的电子邮件地址。 
     */
    email: string = "user@example.com"
    /**
     * OTP 的类型。必须为 `email-verification`，用于确认邮箱更改。
     */
    type: string = "email-verification"
}
```

然后，用户调用 `requestEmailChange()` 时提供该 OTP。系统会先验证发送到当前邮箱的 OTP，然后再发送 OTP 到新邮箱。

### 重写默认的电子邮件验证 [#重写默认的电子邮件验证]

要重写默认的电子邮件验证，可在选项中传入 `overrideDefaultEmailVerification: true`。此设置会让系统改用邮件 OTP 代替默认的验证链接来完成电子邮件验证。换言之，用户将通过输入 OTP 来验证邮箱，而不是点击链接。

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { emailOTP } from "better-auth/plugins"

export const auth = betterAuth({
  plugins: [
    emailOTP({
      overrideDefaultEmailVerification: true, // [!code highlight]
      async sendVerificationOTP({ email, otp, type }) {
        // 实现 sendVerificationOTP 方法，向用户电子邮件发送 OTP
      },
    }),
  ],
});
```

## 选项 [#选项]

* `sendVerificationOTP`: 一个函数，用于向用户的电子邮件地址发送 OTP。该函数接收一个包含以下属性的对象：

  * `email`: 用户的电子邮件地址。
  * `otp`: 要发送的 OTP。
  * `type`: 要发送的 OTP 类型。可以是 `"sign-in"`、`"email-verification"` 或 `"forget-password"`。

  <Callout type="warn">
    建议不要等待电子邮件发送完成，以避免时序攻击。在服务器端平台上，请使用 `waitUntil` 或类似方法确保电子邮件已发送。
  </Callout>

* `otpLength`: OTP 的长度。默认为 `6`。

* `expiresIn`: OTP 的过期时间（秒）。默认为 `300` 秒。

```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { emailOTP } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        emailOTP({
            otpLength: 8,
            expiresIn: 600
        })
    ]
})
```

* `sendVerificationOnSignUp`: 一个布尔值，决定是否在用户注册时发送 OTP。默认为 `false`。

* `disableSignUp`: 一个布尔值，决定是否在用户未注册时阻止自动注册。默认为 `false`。

* `generateOTP`: 生成 OTP 的函数。默认为随机 6 位数字。

* `allowedAttempts`: 验证 OTP 的最大尝试次数。默认为 `3`。超过此限制后，OTP 将失效，用户需要请求新的 OTP。

```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { emailOTP } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        emailOTP({
            allowedAttempts: 5, // 允许 5 次尝试后使 OTP 失效
            expiresIn: 300
        })
    ]
})
```

当超过最大尝试次数时，`verifyOTP`、`signIn.emailOtp`、`verifyEmail` 和 `resetPassword` 方法将返回错误，错误码为 `TOO_MANY_ATTEMPTS`。

* `resendStrategy`: 控制当用户请求新 OTP 时已有有效 OTP 的情况。默认为 `"rotate"`。
  * `"rotate"`: 始终生成新的 OTP（默认行为）。
  * `"reuse"`: 重新发送相同的 OTP 并延长其有效期。这可以防止在电子邮件延迟时同时存在多个有效代码。仅在 OTP 可恢复（`plain`、`encrypted` 或自定义加密/解密）时有效。如果 OTP 是经过哈希处理的，则回退到 `"rotate"`。如果已达到允许的尝试次数，则会生成新的 OTP 而不是重用已耗尽的 OTP。

```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { emailOTP } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        emailOTP({
            resendStrategy: "reuse", // [!code highlight]
            async sendVerificationOTP({ email, otp, type }) {
                // 发送 OTP
            },
        })
    ]
})
```

* `storeOTP`: 在 Better Auth 的验证层存储 OTP 之前对其进行转换的方法，可以是 `encrypted`、`hashed` 或 `plain` 文本。默认为 `plain` 文本。

<Callout>
  注意：这不会影响发送给用户的 OTP。它只影响存储的 OTP 值。存储后端本身由全局 [`verification`](/docs/reference/options#verification) 配置控制，因此如果您配置了 `secondaryStorage`，这些验证记录可以存储在数据库之外。
</Callout>

您也可以传入自定义的加密器或哈希器来控制存储的 OTP 值的持久化方式。

**自定义加密器**

```ts title="auth.ts"
emailOTP({
    storeOTP: { 
        encrypt: async (otp) => {
            return myCustomEncryptor(otp);
        },
        decrypt: async (otp) => {
            return myCustomDecryptor(otp);
        },
    }
})
```

**自定义哈希器**

```ts title="auth.ts"
emailOTP({
    storeOTP: {
        hash: async (otp) => {
            return myCustomHasher(otp);
        },
    }
})
```

