# 用户与账户 (/docs/concepts/users-accounts)

了解如何管理用户和账户，包括更新用户信息、更改邮箱和密码、通过验证删除用户、令牌加密，以及关联和解除关联账户。



除了认证用户外，Better Auth 还提供了一组管理用户的方法。这包括更新用户信息、更改密码等。

用户表存储用户的认证数据 [点击此处查看模式](/docs/concepts/database#用户表)。

用户表可以通过[附加字段](/docs/concepts/database#扩展核心架构)或插件进行扩展，以存储额外的数据。

## 更新用户 [#更新用户]

### 更新用户信息 [#更新用户信息]

要更新用户信息，可以使用客户端提供的 `updateUser` 函数。`updateUser` 函数接收一个具有以下属性的对象：

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

await authClient.updateUser({
    image: "https://example.com/image.jpg",
    name: "John Doe",
})
```

### 更改电子邮件 [#更改电子邮件]

### 更改电子邮件 [#更改电子邮件-1]

要允许用户更改他们的邮箱，首先需要启用默认关闭的 `changeEmail` 功能。将 `changeEmail.enabled` 设置为 `true`：

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

export const auth = betterAuth({
    user: {
        changeEmail: {
            enabled: true,
        }
    },
    emailVerification: {
        // 发送验证邮件所需
        sendVerificationEmail: async ({ user, url, token }) => {
            void sendEmail({
                to: user.email,
            })
        }
    }
})
```

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

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

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

为了增加安全性，你可以在验证邮件发送到新地址之前，要求用户通过**当前**邮箱确认更改。为此，请提供 `sendChangeEmailConfirmation` 函数。

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

export const auth = betterAuth({
    user: {
        changeEmail: {
            enabled: true,
            sendChangeEmailConfirmation: async ({ user, newEmail, url, token }, request) => { 
                void sendEmail({
                    to: user.email, // 发送到当前邮箱
                    subject: '批准邮箱更改',
                    text: `点击链接批准将邮箱更改为 ${newEmail}: ${url}`
                })
            }
        }
    },
    // ...
})
```

#### 无需验证即可更新 [#无需验证即可更新]

如果你允许用户立即更新邮箱（仅当当前邮箱未验证时），可以启用 `updateEmailWithoutVerification`。

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

export const auth = betterAuth({
    user: {
        changeEmail: {
            enabled: true,
            updateEmailWithoutVerification: true
        }
    }
})
```

<Callout type="warn">
  如果 `updateEmailWithoutVerification` 为 false（默认值），则在新邮箱验证之前不会更新邮箱，即使当前邮箱未验证。
</Callout>

#### 客户端用法 [#客户端用法]

在客户端使用 `changeEmail` 函数启动流程。

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

await authClient.changeEmail({
    newEmail: "new-email@email.com",
    callbackURL: "/dashboard", // 验证后跳转
});
```

### 更改密码 [#更改密码]

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

**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
}
```

### 设置密码 [#设置密码]

如果用户通过 OAuth 或其他提供者注册，则不会有密码或凭证账户。在这种情况下，可以使用 `setPassword` 操作为用户设置密码。出于安全考虑，此函数只能在服务器端调用。建议用户通过“忘记密码”流程来设置密码。

```ts title="set-password.ts"
import { auth } from "@/lib/auth"

await auth.api.setPassword({
    body: {
        newPassword: "new-password",
    },
    headers: await headers() // 包含用户会话令牌的请求头
});
```

### 验证密码 [#验证密码]

### 验证密码 [#验证密码-1]

`verifyPassword` 函数允许验证用户当前密码。适用于在执行敏感操作（如更新安全设置）前确认用户身份。此函数只能在服务器端调用。

```ts title="verify-password.ts"
import { auth } from "@/lib/auth"

await auth.api.verifyPassword({
    body: {
        password: "user-password" // 必填
    },
    headers: await headers() // 包含用户会话令牌的请求头
});
```

<Callout type="info">
  对于没有密码的 OAuth 用户，建议使用电子邮件验证或新鲜会话检查来替代敏感操作。
</Callout>

## 删除用户 [#删除用户]

Better Auth 提供了一个工具，可以从数据库硬删除用户。默认禁用，但可以通过传递 `enabled: true` 轻松启用。

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

export const auth = betterAuth({
    //...其他配置
    user: {
        deleteUser: { // [!code highlight]
            enabled: true // [!code highlight]
        } // [!code highlight]
    }
})
```

启用后，可以调用 `authClient.deleteUser` 永久删除数据库中的用户数据。

### 删除前添加验证 [#删除前添加验证]

为了增加安全性，你可能希望在删除账户之前确认用户的意图。一种常见方法是发送验证邮件。Better Auth 提供了 `sendDeleteAccountVerification` 工具来实现此目的。
这在你已设置 OAuth 并且希望用户无需重新登录即可删除账户时尤其需要。

配置示例如下：

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

export const auth = betterAuth({
    user: {
        deleteUser: {
            enabled: true,
            sendDeleteAccountVerification: async (
                {
                    user,   // 用户对象
                    url,    // 自动生成的删除链接
                    token   // 验证令牌（可用于生成自定义链接）
                },
                request  // 原始请求对象（可选）
            ) => {
                // 你的邮件发送逻辑
                // 例如：sendEmail(user.email, "验证删除", url);
            },
        },
    },
});
```

**回调验证工作原理：**

* **回调 URL**: `sendDeleteAccountVerification` 中提供的 URL 是一个预生成的链接，访问后会删除用户数据。

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

await authClient.deleteUser({
    callbackURL: "/goodbye" // 可提供删除后跳转的 URL
});
```

* **身份验证检查**: 用户必须登录到其尝试删除的账户。
  如果未登录，删除过程将失败。

如果已发送自定义 URL，则可以通过带有 token 的 `deleteUser` 方法删除用户。

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

await authClient.deleteUser({
    token
});
```

### 认证要求 [#认证要求]

删除用户需满足以下条件之一：

1. 有效密码

如果用户有密码，提供密码即可删除账户。

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

await authClient.deleteUser({
    password: "password"
});
```

2. 新鲜会话

用户需要拥有一个“新鲜”会话令牌，即最近登录过。如果未提供密码，则会检查此条件。

<Callout type="warn">
  默认情况下 `session.freshAge` 设置为 `60 * 60 * 24`（1 天）。你可以通过向 `auth` 配置传递 `session` 对象来更改此值。如果设置为 `0`，新鲜度检查将被禁用。如果你未使用电子邮件验证来删除账户，建议不要禁用此检查。
</Callout>

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

await authClient.deleteUser();
```

3. 启用邮箱验证（用于 OAuth 用户）

OAuth 用户没有密码，需要发送验证邮件来确认删除意愿。如果已添加 `sendDeleteAccountVerification` 回调，调用 `deleteUser` 无需其他信息即可完成。

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

await authClient.deleteUser();
```

4. 如果你有一个自定义删除账户页面，并通过 `sendDeleteAccountVerification` 回调发送了该 URL。
   那么你需要使用 token 调用 `deleteUser` 方法来完成删除。

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

await authClient.deleteUser({
    token
});
```

### 回调函数 [#回调函数]

**validateUserInfo**：用于决定 Better Auth 接受哪些身份的关卡。它会在创建用户（`create-user`）或关联新提供者账户（`link-account`）之前立即触发，适用于每种认证方式（OAuth、OIDC SSO、SAML SSO、电子邮件/密码、magic link、email OTP、匿名、SIWE、电话号码、管理员创建的用户和 SCIM），也适用于没有持久化数据库的无状态设置，因此策略可以集中在一个地方，而不必按提供者分别配置。

当已有的 **OAuth 或 SSO** 用户再次登录（`sign-in`）时，它也会触发；此时接收的是提供者最新的电子邮件和资料，而不是存储的记录。这使得你可以拒绝其提供者身份已超出范围的用户，例如电子邮件地址不再属于允许的域名。对于非提供者的回访登录不会重新验证，因为其存储记录自创建用户时通过检查后并未改变；如需阻止这些登录，请使用 admin 插件的封禁控制，或使用 `databaseHooks.session.create.before` 钩子。

`source.action` 的值为 `"create-user"`、`"link-account"` 或 `"sign-in"`，而 `source.method` 是认证方式。对于 OAuth，`source.oauth` 包含提供者 id 和原始提供者资料。对于 OIDC 和 SAML SSO，`source.sso` 包含 SSO 提供者 id 以及原始提供者声明或断言属性。

不返回任何内容即可允许创建。返回包含 `error` 的对象即可拒绝：浏览器／重定向流程会将拒绝信息发送到配置的错误 URL，而编程式流程会返回 `403` API 错误。避免在 `errorDescription` 中放入敏感详情，因为它会返回给客户端。

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

export const auth = betterAuth({
    user: {
        validateUserInfo: ({ user, source }) => {
            if (!user.email?.endsWith("@example.com")) {
                return {
                    error: "email_not_allowed",
                    errorDescription: "Use your example.com email to sign in",
                };
            }

            if (
                source.oauth?.providerId === "company-oauth" &&
                source.oauth?.profile?.hd !== "example.com"
            ) {
                return {
                    error: "invalid_organization",
                    errorDescription: "Use your company OAuth account",
                };
            }
        },
    },
});
```

<Callout type="info">
  `validateUserInfo` 是高级策略关卡。较底层的 `databaseHooks.user.create.before` 仍会在之后运行，用于调整数据，也可以中止写入；当你需要修改记录，而不是接受或拒绝身份时，应使用它。
</Callout>

**beforeDelete**：此回调会在删除用户之前调用。你可以使用此回调在删除用户前执行清理或额外检查。

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

export const auth = betterAuth({
    user: {
        deleteUser: {
            enabled: true,
            beforeDelete: async (user) => {
                // 在这里执行清理或额外检查
            },
        },
    },
});
```

你也可以抛出 `APIError` 来中断删除过程。

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

export const auth = betterAuth({
    user: {
        deleteUser: {
            enabled: true,
            beforeDelete: async (user, request) => {
                if (user.email.includes("admin")) {
                    throw new APIError("BAD_REQUEST", {
                        message: "管理员账户无法删除",
                    });
                }
            },
        },
    },
});
```

**afterDelete**：此回调在删除用户后调用。可用于执行清理或其他操作。

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

export const auth = betterAuth({
    user: {
        deleteUser: {
            enabled: true,
            afterDelete: async (user, request) => {
                // 在这里执行清理或其他操作
            },
        },
    },
});
```

## 账户 [#账户]

Better Auth 通过电子邮件和密码、Google 或企业身份提供者等提供者支持多种认证方式。与用户关联的每种方式都会作为一个账户存储。

账户具有本地记录 ID 和提供者身份。`id` 用于标识 Better Auth 账户记录，是传递给账户管理 API 的 `accountId` 值。`providerId` 和 `accountId` 的组合用于标识外部账户：`providerId` 是提供者配置的名称，而 `accountId` 是提供者分配的稳定标识符。有关完整字段列表，请参阅[账户模式](/docs/concepts/database#账户表)。

### 列出用户账户 [#列出用户账户]

使用 `listAccounts` 获取与当前用户关联的所有认证方式。当需要解除账户关联或调用其他特定于账户的 API 时，请保留返回结果中的 `id`。

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

const { data: accounts, error } = await authClient.listAccounts();

if (error) {
    throw new Error(error.message);
}

const googleAccount = accounts?.find((account) => account.providerId === "google");
```

### 令牌加密 [#令牌加密]

Better Auth 默认不加密令牌，这是有意而为。我们希望你对加密和解密的方式拥有完全控制权，而不是内置可能混淆或限制的行为。如果你需要存储加密的令牌（例如 accessToken 或 refreshToken），可以使用数据库钩子（databaseHooks）在保存前进行加密。

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

export const auth = betterAuth({
    databaseHooks: {
        account: {
            create: {
                before(account, context) {
                    const withEncryptedTokens = { ...account };
                    if (account.accessToken) {
                        const encryptedAccessToken = encrypt(account.accessToken)  // [!code highlight]
                        withEncryptedTokens.accessToken = encryptedAccessToken;
                    }
                    if (account.refreshToken) {
                        const encryptedRefreshToken = encrypt(account.refreshToken); // [!code highlight]
                        withEncryptedTokens.refreshToken = encryptedRefreshToken;
                    }
                    return {
                        data: withEncryptedTokens
                    }
                },
            }
        }
    }
})
```

然后每次取回账户时，记得先解密令牌再使用。

### 账户关联 [#账户关联]

账户关联在[默认启用](https://www.better-auth.com/docs/reference/options#accountlinking)，允许用户将多种认证方式关联到同一账户。使用 Better Auth，用户可为现有账户连接额外的社交登录或 OAuth 提供者（前提是提供者确认用户邮箱已验证）。

如果禁用账户关联，则不论提供者或邮箱验证状态如何，均无法关联账户。

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

export const auth = betterAuth({
    account: {
        accountLinking: {
            enabled: false, 
        }
    },
});
```

#### 强制关联 [#强制关联]

你可以指定一组“受信任提供者”。当用户通过该类提供者登录时，即使提供者未确认邮箱验证状态，其账户也会自动关联。请慎用此功能，因可能增加账户被接管风险。

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

export const auth = betterAuth({
    account: {
        accountLinking: {
            enabled: true,
            trustedProviders: ["google", "github"]
        }
    },
});
```

#### 禁用隐式关联 [#禁用隐式关联]

默认情况下，当用户使用邮箱与现有用户匹配的 OAuth 提供者登录时（且该提供者已验证邮箱或位于 `trustedProviders` 中），Better Auth 会自动将该 OAuth 账户关联到该用户。设置 `disableImplicitLinking: true` 可关闭此行为。启用后：

* 对于已有用户，同邮箱的 OAuth 登录会被拒绝，并返回 [`account_not_linked`](/docs/reference/errors/account_not_linked) 错误，而不是被静默关联，即使提供者位于 `trustedProviders` 中或邮箱已验证也是如此。
* 新用户（没有该邮箱对应的现有用户）仍然可以通过 OAuth 注册。
* 已认证的用户仍可通过 [`linkSocial()`](#manually-linking-accounts) 显式关联提供者。

当你希望用户在设置页面中确认关联，而不是在登录时自动完成关联时，可使用此选项。

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

export const auth = betterAuth({
    account: {
        accountLinking: {
            disableImplicitLinking: true,
        }
    },
});
```

#### 手动关联账户 [#手动关联账户]

已登录用户可手动将账户关联到额外的社交提供者或凭证账户。

* **关联社交账户**: 使用客户端的 `linkSocial` 方法将一个社交提供者关联到用户的账户。

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

  await authClient.linkSocial({
      provider: "google", // 要关联的提供者
      callbackURL: "/callback" // 关联完成后的回调 URL
  });
  ```

  你也可以在关联社交账户时请求特定权限范围，与初次认证时的权限不同：

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

  await authClient.linkSocial({
      provider: "google",
      callbackURL: "/callback",
      scopes: ["https://www.googleapis.com/auth/drive.readonly"] // 请求额外权限
  });
  ```

  <Callout type="info">
    新授予的权限范围会合并到 `account.scope` 中，因此之前授予的权限仍会保留。重新进行登录认证和刷新令牌响应不会修改 `account.scope`。
  </Callout>

  你也可以直接使用 ID 令牌关联账户，而无需重定向到提供者的 OAuth 流程：

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

  await authClient.linkSocial({
      provider: "google",
      idToken: {
          token: "id_token_from_provider",
          nonce: "nonce_used_for_token", // 可选
          accessToken: "access_token", // 可选，部分提供者需要
          refreshToken: "refresh_token" // 可选
      }
  });
  ```

  这在以下情况下非常有用：

  * 在使用原生 SDK 登录后
  * 当使用处理身份验证的移动应用程序
  * 当实现自定义 OAuth 流程时

  ID 令牌必须有效且提供者支持验证。

  如果希望用户可以用与账户邮箱不同的邮箱关联社交账户，或使用不返回邮箱的提供者，需要在账户关联设置中启用此功能。

  如果希望新关联的账户更新用户信息，需要在账户关联设置中启用此功能。

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

  export const auth = betterAuth({
      account: {
          accountLinking: {
              allowDifferentEmails: true
          }
      },
  });
  ```

  默认情况下，关联账户不会影响现有用户资料。启用 `updateUserInfoOnLink` 后，每次关联账户时都会将提供者的资料复制到用户。同步的字段与注册时持久化的字段相同（`name`、`image`，以及 `mapProfileToUser` 添加的任何允许输入字段）。用户的 `email` 和 `emailVerified` 在关联时永远不会被更改，因此关联提供者不会重新绑定该账户的身份。

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

  export const auth = betterAuth({
      account: {
          accountLinking: {
              updateUserInfoOnLink: true
          }
      },
  });
  ```

* **关联基于凭证的账户**: 要关联基于凭证的账户（例如邮箱和密码），用户可以发起“忘记密码”流程，或者你可以在服务器端调用 `setPassword` 方法。

  ```ts
  import { auth } from "@/lib/auth"

  await auth.api.setPassword({
    body: {
        newPassword: "new-password", // 必填
    },
    headers: await headers() // 包含用户会话令牌的请求头
  });
  ```

<Callout>
  出于安全考虑，不能从客户端调用 `setPassword`。
</Callout>

### 账户解除关联 [#账户解除关联]

通过传递 Better Auth 账户记录的 `id` 来解除账户关联，该值可以从 `listAccounts` 获取。

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

const { data: accounts, error } = await authClient.listAccounts();

if (error) {
    throw new Error(error.message);
}

const account = accounts?.find((account) => account.providerId === "google");

if (account) {
    await authClient.unlinkAccount({
        accountId: account.id,
    });
}
```

如果账户不存在或不属于当前用户，Better Auth 会返回错误。除非 `allowUnlinkingAll` 为 `true`，否则 Better Auth 还会阻止用户解除其唯一账户的关联。

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

export const auth = betterAuth({
    account: {
        accountLinking: {
            allowUnlinkingAll: true
        }
    },
});
```

