# 电子邮件和密码 (/docs/authentication/email-password)

使用 Better Auth 实现电子邮件和密码认证。



电子邮件和密码认证是许多应用常用的一种方法。Better Auth 提供了内置的电子邮件和密码认证器，您可以轻松集成到项目中。

<Callout type="info">
  如果您更喜欢基于用户名的认证，请查看{" "}
  <Link href="/docs/plugins/username">用户名插件</Link>。它扩展了
  电子邮件和密码认证器，支持用户名。
</Callout>

## 启用电子邮件和密码 [#启用电子邮件和密码]

要启用电子邮件和密码认证，您需要在 `auth` 配置的 `emailAndPassword.enabled` 选项中设置为 `true`。

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

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

<Callout type="info">
  如果未启用，则不允许您使用电子邮件和密码进行登录或注册。
</Callout>

此外，为了使用以下服务器方法（如 `signUpEmail`），cookie 必须返回给客户端。这可能需要额外的配置。为 [Next](/docs/integrations/next#服务器操作中的-cookie) 和 [SvelteKit](/docs/integrations/svelte-kit#服务器-action-的-cookies) 提供了插件。

## 使用说明 [#使用说明]

### 注册 [#注册]

要注册用户，可以使用客户端提供的 `signUp.email` 函数。

**Endpoint:** `POST /sign-up/email`

### Client Side

```ts
const { data, error } = await authClient.signUp.email({
    name: "John Doe", // required, 用户的姓名。
    email: "john.doe@example.com", // required, 用户的电子邮件地址。
    password: "password1234", // required, 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
    image: "https://example.com/image.png", // 可选的用户头像。
    callbackURL: "https://example.com/callback", // 可选的重定向 URL，用户注册后将被重定向到该地址。
});
```

### Server Side

```ts
const data = await auth.api.signUpEmail({
    body: {
        name: "John Doe", // required, 用户的姓名。
        email: "john.doe@example.com", // required, 用户的电子邮件地址。
        password: "password1234", // required, 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
        image: "https://example.com/image.png", // 可选的用户头像。
        callbackURL: "https://example.com/callback", // 可选的重定向 URL，用户注册后将被重定向到该地址。
    },
});
```

### Type Definition

```ts
type signUpEmail = {
    /**
     * 用户的姓名。
     */
    name: string = "John Doe"
    /**
     * 用户的电子邮件地址。
     */
    email: string = "john.doe@example.com"
    /**
     * 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
     */
    password: string = "password1234"
    /**
     * 可选的用户头像。
     */
    image?: string = "https://example.com/image.png"
    /**
     * 可选的重定向 URL，用户注册后将被重定向到该地址。
     */
    callbackURL?: string = "https://example.com/callback"
}
```

<Callout>
  以上是注册邮箱端点的默认属性，但通过[额外字段](/docs/concepts/typescript#额外字段)或特殊插件，您可以向端点传递更多属性。
</Callout>

### 登录 [#登录]

要登录用户，可以使用客户端提供的 `signIn.email` 函数。

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

### Client Side

```ts
const { data, error } = await authClient.signIn.email({
    email: "john.doe@example.com", // required, 用户的电子邮件地址。
    password: "password1234", // required, 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
    rememberMe: true, // 如果为 false，用户将在浏览器关闭时登出。（可选）默认值：true
    callbackURL: "https://example.com/callback", // 可选的重定向 URL，用户登录后将被重定向到该地址。（可选）
});
```

### Server Side

```ts
const data = await auth.api.signInEmail({
    body: {
        email: "john.doe@example.com", // required, 用户的电子邮件地址。
        password: "password1234", // required, 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
        rememberMe: true, // 如果为 false，用户将在浏览器关闭时登出。（可选）默认值：true
        callbackURL: "https://example.com/callback", // 可选的重定向 URL，用户登录后将被重定向到该地址。（可选）
    },
});
```

### Type Definition

```ts
type signInEmail = {
    /**
     * 用户的电子邮件地址。
     */
    email: string = "john.doe@example.com"
    /**
     * 用户的密码。默认至少为 8 个字符，最多为 128 个字符。
     */
    password: string = "password1234"
    /**
     * 如果为 false，用户将在浏览器关闭时登出。（可选）默认值：true
     */
    rememberMe?: boolean = true
    /**
     * 可选的重定向 URL，用户登录后将被重定向到该地址。（可选）
     */
    callbackURL?: string = "https://example.com/callback"
}
```

<Callout>
  以上是登录邮箱端点的默认属性，但通过[额外字段](/docs/concepts/typescript#额外字段)或特殊插件，您可以向端点传递不同的属性。
</Callout>

### 注销 [#注销]

要注销用户，可以使用客户端提供的 `signOut` 函数。

**Endpoint:** `POST /sign-out`

### Client Side

```ts
await authClient.signOut();
```

### Server Side

```ts
await auth.api.signOut({
    // Pass the current request headers so Better Auth can read and set cookies.
    headers: await headers(),
});
```

### Type Definition

```ts
type signOut = {
}
```

您可以在 `fetchOptions` 中传递 `onSuccess` 以在成功后重定向：

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

await authClient.signOut({
  fetchOptions: {
    onSuccess: () => {
      router.push("/login"); // 重定向到登录页面
    },
  },
});
```

### 邮箱验证 [#邮箱验证]

要启用邮箱验证，您需要传递一个发送验证邮件的函数，该函数发送包含验证链接的邮件。`sendVerificationEmail` 函数接收一个包含以下属性的数据对象：

* `user`: 用户对象。
* `url`: 要发送给用户的 URL，其中包含令牌。
* `token`: 用于完成邮箱验证的验证令牌。

以及作为第二个参数的 `request` 对象。

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // 您的发送邮件函数

export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ( { user, url, token }, request) => {
      void sendEmail({
        to: user.email,
        subject: "请验证您的电子邮件地址",
        text: `点击链接验证您的电子邮件：${url}`,
      });
    },
  },
});
```

<Callout type="warn">
  避免等待邮件发送，以防止时序攻击。在无服务器平台上，请使用 `waitUntil` 或类似机制确保邮件已发送。
</Callout>

客户端可以调用 `sendVerificationEmail` 函数向用户发送验证链接。这将触发您在 `auth` 配置中提供的 `sendVerificationEmail` 函数。

当用户点击邮件中的链接时，如果令牌有效，将重定向到 `callbackURL` 参数提供的 URL；如果令牌无效，则会重定向到带有错误消息的 `callbackURL`（查询字符串为 `?error=invalid_token`）。

#### 需要邮箱验证 [#需要邮箱验证]

如果启用此功能，用户必须验证邮箱后才能登录。每当用户尝试登录时，将调用 `sendVerificationEmail`。

<Callout>
  对于电子邮件和密码登录，只有在您实现了
  `sendVerificationEmail` 时，此功能才会生效。社交登录不受此设置限制；
  它有自己的可选配置，即各提供商的
  [`requireEmailVerification`](/docs/concepts/oauth#requireemailverification)。

  开启 `requireEmailVerification` 时，使用已存在的邮箱注册会返回成功响应，而非错误，以防止用户枚举。
</Callout>

```ts title="auth.ts"
export const auth = betterAuth({
  emailAndPassword: {
    requireEmailVerification: true, // [!code highlight]
  },
});
```

您可以用 `onExistingUserSignUp` 回调通知已有用户有人尝试用他们的邮箱注册：

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // 您的发送邮件函数

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    onExistingUserSignUp: async ({ user }, request) => {
      void sendEmail({
        to: user.email,
        subject: "有人尝试用您的邮箱注册",
        text: "有人试图使用您的邮箱地址创建账号。如果是您，请尝试登录；如果不是，可以忽略这封邮件。",
      });
    },
  },
});
```

如果用户尝试在未验证邮箱时登录，您可以捕获错误并给用户显示提示。

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

await authClient.signIn.email(
  {
    email: "email@example.com",
    password: "password",
  },
  {
    onError: (ctx) => {
      // 处理错误
      if (ctx.error.status === 403) {
        alert("请先验证您的电子邮件地址");
      }
      // 也可以显示原始错误信息
      alert(ctx.error.message);
    },
  }
);
```

#### 邮箱枚举保护 [#邮箱枚举保护]

当启用 `requireEmailVerification` 或将 `autoSignIn` 设为 `false` 时，注册端点会通过统一返回 `200` 响应，来防止邮箱枚举，无论邮箱是否已注册。此做法符合 [OWASP 认证最佳实践](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-and-error-messages)。

<Callout type="info">
  这一保护只在注册响应不包含 session 令牌时生效——即 `requireEmailVerification` 为 `true` 或 `autoSignIn` 为 `false`。默认配置下，已存在邮箱仍会返回 `422` 错误。
</Callout>

类似地，`/change-email` 端点不再透露目标邮箱是否已注册 —— 总是返回成功响应。

##### 为用户字段添加插件 [#为用户字段添加插件]

如果您使用向用户表添加字段的插件（例如 [admin](/docs/plugins/admin)、[two-factor](/docs/plugins/2fa)、[phone-number](/docs/plugins/phone-number)），合成响应需要包含这些字段，以便与真实注册响应无法区分。使用 `customSyntheticUser` 选项构建完整的用户对象：

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

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    customSyntheticUser: ({ coreFields, additionalFields, id }) => ({
      ...coreFields,
      // Admin 插件字段（按数据库字段顺序）
      role: "user",
      banned: false,
      banReason: null,
      banExpires: null,
      // 您的额外字段
      ...additionalFields,
      // ID 必须放后以匹配数据库输出顺序
      id,
    }),
  },
  plugins: [admin()],
});
```

回调接收三个构建块：

* **`coreFields`** — `name`、`email`、`emailVerified`、`image`、`createdAt`、`updatedAt`
* **`additionalFields`** — 含默认值的 `user.additionalFields`
* **`id`** — 生成的用户 ID，符合您的 ID 策略

您应依数据库字段顺序组装：核心字段 → 插件字段 → 额外字段 → ID。每个插件会说明所需字段 — 请参考 [admin 插件](/docs/plugins/admin#电子邮件枚举保护) 示例。

#### 手动触发邮件验证 [#手动触发邮件验证]

您可以调用 `sendVerificationEmail` 函数手动触发邮箱验证。

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

await authClient.sendVerificationEmail({
  email: "user@email.com",
  callbackURL: "/", // 验证后的重定向 URL
});
```

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

允许用户重置密码前，您需要为电子邮件和密码认证器提供 `sendResetPassword` 函数。该函数接收一个包含以下属性的数据对象：

* `user`: 用户对象。
* `url`: 要发送给用户的 URL，其中包含令牌。
* `token`: 用于完成密码重置的验证令牌。

及作为第二参数的 `request` 对象。

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // 您的发送邮件函数

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    sendResetPassword: async ({user, url, token}, request) => {
      void sendEmail({
        to: user.email,
        subject: "重置您的密码",
        text: `点击链接重置您的密码：${url}`,
      });
    },
    onPasswordReset: async ({ user }, request) => {
      // 您的逻辑
      console.log(`用户 ${user.email} 的密码已被重置。`);
    },
  },
});
```

<Callout type="warn">
  避免等待邮件发送，以防止时序攻击。在无服务器平台上，请使用 `waitUntil` 或类似机制确保邮件已发送。
</Callout>

此外，您可以提供 `onPasswordReset` 回调，在密码成功重置后执行逻辑。新密码保存后会调用此回调；启用 [`revokeSessionsOnPasswordReset`](#revoking-sessions-on-password-reset) 时，则会在用户会话撤销后调用。如果回调抛出异常，请求会返回错误，但密码重置已经生效。

服务器配置完成后，您可以调用 `requestPasswordReset` 函数向用户发送密码重置链接。如果用户存在，会触发您在认证配置中提供的 `sendResetPassword` 函数。

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

### Client Side

```ts
const { data, error } = await authClient.requestPasswordReset({
    email: "john.doe@example.com", // required, 要发送密码重置邮件的用户电子邮件地址
    redirectTo: "https://example.com/reset-password", // 要将用户重定向到以重置密码的 URL。如果令牌无效或已过期，将重定向到带有查询参数 `?error=INVALID_TOKEN` 的 URL。如果令牌有效，将重定向到带有查询参数 `?token=VALID_TOKEN` 的 URL。
});
```

### Server Side

```ts
const data = await auth.api.requestPasswordReset({
    body: {
        email: "john.doe@example.com", // required, 要发送密码重置邮件的用户电子邮件地址
        redirectTo: "https://example.com/reset-password", // 要将用户重定向到以重置密码的 URL。如果令牌无效或已过期，将重定向到带有查询参数 `?error=INVALID_TOKEN` 的 URL。如果令牌有效，将重定向到带有查询参数 `?token=VALID_TOKEN` 的 URL。
    },
});
```

### Type Definition

```ts
type requestPasswordReset = {
    /**
     * 要发送密码重置邮件的用户电子邮件地址
     */
    email: string = "john.doe@example.com"
    /**
     * 要将用户重定向到以重置密码的 URL。如果令牌无效或已过期，将重定向到带有查询参数 `?error=INVALID_TOKEN` 的 URL。如果令牌有效，将重定向到带有查询参数 `?token=VALID_TOKEN` 的 URL。
     */
    redirectTo?: string = "https://example.com/reset-password"
}
```

用户点击邮件中的链接后，将被重定向到密码重置页。您可以在应用中添加该页面，然后调用 `resetPassword` 函数完成密码重置。它接收一个包含以下属性的对象：

* `newPassword`: 用户的新密码。

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

const { data, error } = await authClient.resetPassword({
  newPassword: "password1234",
  token,
});
```

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

### Client Side

```ts
const token = new URLSearchParams(window.location.search).get("token");

if (!token) {
  // 处理错误
}
const { data, error } = await authClient.resetPassword({
    newPassword: "password1234", // required, 要设置的新密码
    token, // required, 用于重置密码的令牌
});
```

### Server Side

```ts
const token = new URLSearchParams(window.location.search).get("token");

if (!token) {
  // 处理错误
}
const data = await auth.api.resetPassword({
    body: {
        newPassword: "password1234", // required, 要设置的新密码
        token, // required, 用于重置密码的令牌
    },
});
```

### Type Definition

```ts
type resetPassword = {
    /**
     * 要设置的新密码
     */
    newPassword: string = "password1234"
    /**
     * 用于重置密码的令牌
     */
    token: string
}
```

#### 重置密码时撤销会话 [#重置密码时撤销会话]

默认情况下，用户重置密码时不会撤销其他活动会话。要在重置密码时撤销用户的所有会话，请将 `revokeSessionsOnPasswordReset` 设置为 `true`：

```ts title="auth.ts"
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    revokeSessionsOnPasswordReset: true, // [!code highlight]
    sendResetPassword: async ({ user, url, token }, request) => {
      // your email sending logic
    },
  },
});
```

Better Auth 会先检查新密码，再消耗重置令牌。因此，如果密码被拒绝（例如被 [Have I Been Pwned](/docs/plugins/have-i-been-pwned) 拒绝），重置链接仍可继续使用。

对于存储在数据库中的会话，当数据库适配器启用事务时，会话撤销与密码修改要么一起成功，要么一起失败。Drizzle 和 Prisma 适配器仅在传入 `transaction: true` 时使用事务。独立 MongoDB 服务器和 Cloudflare D1 不支持事务。Better Auth 会在一个事务中消耗重置令牌、撤销会话并保存新密码；任何步骤失败时，请求都会返回错误，旧密码保持不变。如果验证值存储在数据库中，同一个重置链接便可再次使用。未配置二级存储时默认如此；使用[二级存储](/docs/concepts/database#secondary-storage)时，请设置 [`verification.storeInDatabase: true`](/docs/reference/options#verification)。否则令牌会被消耗，用户需要新的链接。[邮箱 OTP](/docs/plugins/email-otp)和[电话号码](/docs/plugins/phone-number)重置端点的 OTP 也遵循相同规则。

以下两种情况的保证较弱：

* 如果数据库适配器未启用事务，令牌会先被消耗，会话也会在保存密码之前撤销。如果之后保存密码失败，旧密码仍然有效，但会话（包括二级存储中的副本）已经撤销，用户需要新的重置链接或 OTP。
* 使用[二级存储](/docs/concepts/database#secondary-storage)时，只有数据库操作提交后才会从二级存储中删除会话。如果删除失败，系统会记录日志而不向请求返回错误，因此新密码仍会生效，而这些会话可能一直有效到过期为止。

### 更新密码 [#更新密码]

用户的密码不会存储在用户表中。相反，它存储在账户表中。要更改用户的密码，您可以使用以下方法之一：

**Endpoint:** `POST /change-password`

### Client Side

```ts
const { data, error } = await authClient.changePassword({
    newPassword: "newpassword1234", // required, 要设置的新密码
    currentPassword: "oldpassword1234", // required, 当前用户密码
    revokeOtherSessions: true, // 如果设置为 true，此用户的所有其他活动会话将被失效
});
```

### Server Side

```ts
const data = await auth.api.changePassword({
    body: {
        newPassword: "newpassword1234", // required, 要设置的新密码
        currentPassword: "oldpassword1234", // required, 当前用户密码
        revokeOtherSessions: true, // 如果设置为 true，此用户的所有其他活动会话将被失效
    },
    // This endpoint requires session cookies.
    headers: await headers(),
});
```

### Type Definition

```ts
type changePassword = {
    /**
     * 要设置的新密码
     */
    newPassword: string = "newpassword1234"
    /**
     * 当前用户密码
     */
    currentPassword: string = "oldpassword1234"
    /**
     * 如果设置为 true，此用户的所有其他活动会话将被失效
     */
    revokeOtherSessions?: boolean = true
}
```

### 配置 [#配置]

**密码**

Better Auth 将密码存储在 `account` 表中，`providerId` 设置为 `credential`。

**密码哈希**：Better Auth 使用 `scrypt` 来哈希密码。`scrypt` 算法设计为计算缓慢且占用内存，从而让攻击者难以破解密码。OWASP 建议在没有 `argon2id` 的情况下使用 `scrypt`。我们选择 `scrypt` 是因为它被 Node.js 原生支持。

您可以通过在 `emailAndPassword` 配置中设置 `password` 选项，传入自定义的密码哈希算法。

**示例**

下面演示如何自定义密码哈希使用 Argon2：

下面是使用 Argon2 自定义密码哈希的示例：

```ts title="password.ts"
import { hash, type Options, verify } from "@node-rs/argon2";

const opts: Options = {
  memoryCost: 65536, // 64 MiB
  timeCost: 3, // 3 次迭代
  parallelism: 4, // 4 个线程
  outputLen: 32, // 32 字节
  algorithm: 2, // Argon2id
};

export async function hashPassword(password: string) {
  const result = await hash(password, opts);
  return result;
}

export async function verifyPassword(data: { password: string; hash: string }) {
  const { password, hash } = data;
  const result = await verify(hash, password, opts);
  return result;
}
```

```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { hashPassword, verifyPassword } from "./password";

export const auth = betterAuth({
  emailAndPassword: {
    //...其余选项
    enabled: true,
    password: {
      hash: hashPassword,
      verify: verifyPassword,
    },
  },
});
```



<TypeTable type="emailPasswordOptionsType" />

