better-auth-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Better Auth — Best Practices for Common Features

Better Auth — 常用功能最佳实践

This skill combines the three highest-traffic Better Auth feature areas. Pick the section that matches what you're implementing — the parent
better-auth
skill has the integration story;
better-auth-security
covers cross-cutting hardening; this one covers feature-by-feature best practices.
FeatureSection
Email + password (verification, reset, hashing, policies)Email/Password
Organizations, teams, RBACOrganization plugin
Two-factor authentication (TOTP, backup codes, recovery)Two-Factor (2FA)
For Tauri-specific integration, see
better-auth-tauri-setup
. For OAuth/social providers, see
better-auth-providers
. For the broader integration overview, see
better-auth
.

本指南整合了Better Auth流量最高的三大功能领域。选择与你正在实现的功能匹配的章节——父级
better-auth
技能包含集成说明;
better-auth-security
涵盖跨领域安全强化;本指南则针对每个功能提供最佳实践。
功能章节
邮箱+密码(验证、重置、哈希、策略)邮箱/密码
组织、团队、RBAC组织插件
双因素认证(TOTP、备份码、恢复)双因素认证(2FA)
针对Tauri的特定集成,请查看
better-auth-tauri-setup
。关于OAuth/社交登录提供商,请查看
better-auth-providers
。如需更全面的集成概述,请查看
better-auth

Email/Password

邮箱/密码

Quick Start

快速开始

  1. Enable email/password:
    emailAndPassword: { enabled: true }
  2. Configure
    emailVerification.sendVerificationEmail
  3. Add
    sendResetPassword
    for password reset flows
  4. Run
    npx @better-auth/cli@latest migrate
  5. Verify: attempt sign-up and confirm verification email triggers
  1. 启用邮箱/密码认证:
    emailAndPassword: { enabled: true }
  2. 配置
    emailVerification.sendVerificationEmail
  3. 添加
    sendResetPassword
    以支持密码重置流程
  4. 运行
    npx @better-auth/cli@latest migrate
  5. 验证:尝试注册并确认验证邮件是否触发

Email Verification Setup

邮箱验证设置

Configure
emailVerification.sendVerificationEmail
to verify user email addresses.
配置
emailVerification.sendVerificationEmail
以验证用户邮箱地址。

Requiring Email Verification

强制邮箱验证

For stricter security, enable
emailAndPassword.requireEmailVerification
to block sign-in until the user verifies their email.
为提升安全性,启用
emailAndPassword.requireEmailVerification
,阻止用户在验证邮箱前登录。

Client Side Validation

客户端验证

Implement client-side validation for immediate user feedback and reduced server load.
实现客户端验证,为用户提供即时反馈并减少服务器负载。

Callback URLs

回调URL

Always use absolute URLs (including the origin) for callback URLs in sign-up and sign-in requests.
在注册和登录请求中,回调URL始终使用绝对URL(包含域名)。

Password Reset Flows

密码重置流程

Provide
sendResetPassword
in the email and password config to enable password resets.
在邮箱和密码配置中提供
sendResetPassword
以启用密码重置功能。

Security Considerations

安全考量

Built-in protections: background email sending (timing attack prevention), dummy operations on invalid requests, constant response messages regardless of user existence.
On serverless platforms, configure a background task handler using
waitUntil
.
内置防护措施:后台发送邮件(防止时序攻击)、对无效请求执行虚拟操作、无论用户是否存在均返回一致响应信息。
在无服务器平台上,使用
waitUntil
配置后台任务处理程序。

Token Security

令牌安全

Tokens expire after 1 hour by default. Configure with
resetPasswordTokenExpiresIn
(in seconds). Tokens are single-use.
令牌默认1小时后过期。可通过
resetPasswordTokenExpiresIn
(单位:秒)进行配置。令牌为一次性使用。

Session Revocation

会话吊销

Enable
revokeSessionsOnPasswordReset
to invalidate all existing sessions on password reset.
启用
revokeSessionsOnPasswordReset
,在密码重置时使所有现有会话失效。

Password Requirements

密码要求

Password length limits configurable via
minPasswordLength
and
maxPasswordLength
.
可通过
minPasswordLength
maxPasswordLength
配置密码长度限制。

Password Hashing

密码哈希

Default:
scrypt
(Node.js native, no external dependencies).
默认算法:
scrypt
(Node.js原生算法,无外部依赖)。

Custom Hashing Algorithm

自定义哈希算法

To use Argon2id or another algorithm, provide custom
hash
and
verify
functions.
Note: If you switch hashing algorithms on an existing system, users with passwords hashed using the old algorithm won't be able to sign in. Plan a migration strategy if needed.
<!-- cross-ref:start -->
如需使用Argon2id或其他算法,请提供自定义
hash
verify
函数。
注意:如果在现有系统中切换哈希算法,使用旧算法哈希密码的用户将无法登录。如有需要,请规划迁移策略。
<!-- cross-ref:start -->

See also (related skills — Better Auth family)

相关技能(Better Auth系列)

If your issue relates to:
  • Better Auth integration overview — check
    better-auth
    if appropriate.
  • best-practices guide — check
    better-auth-best-practices
    if appropriate.
  • create the auth layer (initial scaffolding) — check
    better-auth-create-auth
    if appropriate.
  • explain a specific error code + provide fix — check
    better-auth-explain-error
    if appropriate.
  • organization/team plugin — check
    better-auth-organization
    if appropriate.
  • OAuth/email/magic-link/social provider config — check
    better-auth-providers
    if appropriate.
  • rate limit, CSRF, trusted origins, secrets, OAuth security — check
    better-auth-security
    if appropriate.
  • twoFactor plugin enforcement — check
    better-auth-two-factor
    if appropriate.
  • wiring Better Auth into a Tauri desktop app — check
    better-auth-tauri-setup
    if appropriate.
  • Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check
    better-auth-tauri-pitfalls
    if appropriate.
  • reproduction guide for the better-auth-ui crash on Tauri v2 — check
    sawy-better-auth-ui-tauri-repro
    if appropriate.
<!-- cross-ref:end -->
如果你的问题涉及:
  • Better Auth集成概述 — 请查看
    better-auth
  • 最佳实践指南 — 请查看
    better-auth-best-practices
  • 创建认证层(初始脚手架) — 请查看
    better-auth-create-auth
  • 解释特定错误码并提供修复方案 — 请查看
    better-auth-explain-error
  • 组织/团队插件 — 请查看
    better-auth-organization
  • OAuth/邮箱/魔法链接/社交登录提供商配置 — 请查看
    better-auth-providers
  • 速率限制、CSRF、可信来源、密钥、OAuth安全 — 请查看
    better-auth-security
  • twoFactor插件强制实施 — 请查看
    better-auth-two-factor
  • 在Tauri桌面应用中集成Better Auth — 请查看
    better-auth-tauri-setup
  • Tauri特定问题(Cookie、深度链接、macOS、404回调) — 请查看
    better-auth-tauri-pitfalls
  • Tauri v2上better-auth-ui崩溃的复现指南 — 请查看
    sawy-better-auth-ui-tauri-repro
<!-- cross-ref:end -->

Organization plugin

组织插件

Setting Up Organizations

设置组织功能

When adding organizations to your application, configure the
organization
plugin with appropriate limits and permissions.
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      organizationLimit: 5, // Max orgs per user
      membershipLimit: 100, // Max members per org
    }),
  ],
});
Note: After adding the plugin, run
npx @better-auth/cli migrate
to add the required database tables.
在应用中添加组织功能时,配置
organization
插件并设置合适的限制和权限。
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      organizationLimit: 5, // 每个用户最多创建的组织数
      membershipLimit: 100, // 每个组织最多成员数
    }),
  ],
});
注意:添加插件后,运行
npx @better-auth/cli migrate
以添加所需的数据库表。

Client-Side Setup

客户端设置

Add the client plugin to access organization methods:
ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [organizationClient()],
});
添加客户端插件以访问组织相关方法:
ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [organizationClient()],
});

Creating Organizations

创建组织

Organizations are the top-level entity for grouping users. When created, the creator is automatically assigned the
owner
role.
ts
const createOrg = async () => {
  const { data, error } = await authClient.organization.create({
    name: "My Company",
    slug: "my-company",
    logo: "https://example.com/logo.png",
    metadata: { plan: "pro" },
  });
};
组织是用于分组用户的顶级实体。创建时,创建者将自动被分配
owner
角色。
ts
const createOrg = async () => {
  const { data, error } = await authClient.organization.create({
    name: "我的公司",
    slug: "my-company",
    logo: "https://example.com/logo.png",
    metadata: { plan: "pro" },
  });
};

Controlling Organization Creation

控制组织创建权限

Restrict who can create organizations based on user attributes:
ts
organization({
  allowUserToCreateOrganization: async (user) => {
    return user.emailVerified === true;
  },
  organizationLimit: async (user) => {
    // Premium users get more organizations
    return user.plan === "premium" ? 20 : 3;
  },
});
根据用户属性限制可创建组织的用户:
ts
organization({
  allowUserToCreateOrganization: async (user) => {
    return user.emailVerified === true;
  },
  organizationLimit: async (user) => {
    // 付费用户可创建更多组织
    return user.plan === "premium" ? 20 : 3;
  },
});

Creating Organizations on Behalf of Users

代表用户创建组织

Administrators can create organizations for other users (server-side only):
ts
await auth.api.createOrganization({
  body: {
    name: "Client Organization",
    slug: "client-org",
    userId: "user-id-who-will-be-owner", // `userId` is required
  },
});
Note: The
userId
parameter cannot be used alongside session headers.
管理员可以为其他用户创建组织(仅支持服务器端):
ts
await auth.api.createOrganization({
  body: {
    name: "客户组织",
    slug: "client-org",
    userId: "将成为所有者的用户ID", // `userId`为必填项
  },
});
注意
userId
参数不能与会话头一起使用。

Active Organizations

活跃组织

The active organization is stored in the session and scopes subsequent API calls. Always set an active organization after the user selects one.
ts
const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};
Many endpoints use the active organization when
organizationId
is not provided:
ts
// These use the active organization automatically
await authClient.organization.listMembers();
await authClient.organization.listInvitations();
await authClient.organization.inviteMember({ email: "user@example.com", role: "member" });
活跃组织存储在会话中,并限定后续API调用的范围。用户选择组织后,务必设置活跃组织。
ts
const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};
当未提供
organizationId
时,许多端点会自动使用活跃组织:
ts
// 这些操作会自动使用活跃组织
await authClient.organization.listMembers();
await authClient.organization.listInvitations();
await authClient.organization.inviteMember({ email: "user@example.com", role: "member" });

Getting Full Organization Data

获取完整组织数据

Retrieve the active organization with all its members, invitations, and teams:
ts
const { data } = await authClient.organization.getFullOrganization();
// data.organization, data.members, data.invitations, data.teams
获取包含所有成员、邀请和团队信息的活跃组织:
ts
const { data } = await authClient.organization.getFullOrganization();
// data.organization, data.members, data.invitations, data.teams

Members

成员

Members are users who belong to an organization. Each member has a role that determines their permissions.
成员是属于某个组织的用户。每个成员都有一个决定其权限的角色。

Adding Members (Server-Side)

添加成员(服务器端)

Add members directly without invitations (useful for admin operations):
ts
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: "member",
    organizationId: "org-id",
  },
});
Note: For client-side member additions, use the invitation system instead.
直接添加成员而无需发送邀请(适用于管理员操作):
ts
await auth.api.addMember({
  body: {
    userId: "用户ID",
    role: "member",
    organizationId: "组织ID",
  },
});
注意:客户端添加成员请使用邀请系统。

Assigning Multiple Roles

分配多个角色

Members can have multiple roles for fine-grained permissions:
ts
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: ["admin", "moderator"],
    organizationId: "org-id",
  },
});
成员可拥有多个角色以实现细粒度权限控制:
ts
await auth.api.addMember({
  body: {
    userId: "用户ID",
    role: ["admin", "moderator"],
    organizationId: "组织ID",
  },
});

Removing Members

删除成员

Remove members by ID or email:
ts
await authClient.organization.removeMember({
  memberIdOrEmail: "user@example.com",
});
Important: The last owner cannot be removed. Assign the owner role to another member first.
通过ID或邮箱删除成员:
ts
await authClient.organization.removeMember({
  memberIdOrEmail: "user@example.com",
});
重要:最后一位所有者无法被删除。请先将所有者角色分配给其他成员。

Updating Member Roles

更新成员角色

ts
await authClient.organization.updateMemberRole({
  memberId: "member-id",
  role: "admin",
});
ts
await authClient.organization.updateMemberRole({
  memberId: "成员ID",
  role: "admin",
});

Membership Limits

成员数量限制

Control the maximum number of members per organization:
ts
organization({
  membershipLimit: async (user, organization) => {
    if (organization.metadata?.plan === "enterprise") {
      return 1000;
    }
    return 50;
  },
});
控制每个组织的最大成员数:
ts
organization({
  membershipLimit: async (user, organization) => {
    if (organization.metadata?.plan === "enterprise") {
      return 1000;
    }
    return 50;
  },
});

Invitations

邀请

The invitation system allows admins to invite users via email. Configure email sending to enable invitations.
邀请系统允许管理员通过邮箱邀请用户。配置邮件发送功能以启用邀请。

Setting Up Invitation Emails

设置邀请邮件

ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      sendInvitationEmail: async (data) => {
        const { email, organization, inviter, invitation } = data;

        await sendEmail({
          to: email,
          subject: `Join ${organization.name}`,
          html: `
            <p>${inviter.user.name} invited you to join ${organization.name}</p>
            <a href="https://yourapp.com/accept-invite?id=${invitation.id}">
              Accept Invitation
            </a>
          `,
        });
      },
    }),
  ],
});
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      sendInvitationEmail: async (data) => {
        const { email, organization, inviter, invitation } = data;

        await sendEmail({
          to: email,
          subject: `加入${organization.name}`,
          html: `
            <p>${inviter.user.name}邀请你加入${organization.name}</p>
            <a href="https://yourapp.com/accept-invite?id=${invitation.id}">
              接受邀请
            </a>
          `,
        });
      },
    }),
  ],
});

Sending Invitations

发送邀请

ts
await authClient.organization.inviteMember({
  email: "newuser@example.com",
  role: "member",
});
ts
await authClient.organization.inviteMember({
  email: "newuser@example.com",
  role: "member",
});

Creating Shareable Invitation URLs

创建可分享的邀请URL

For sharing via Slack, SMS, or in-app notifications:
ts
const { data } = await authClient.organization.getInvitationURL({
  email: "newuser@example.com",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// Share data.url via any channel
Note: This endpoint does not call
sendInvitationEmail
. Handle delivery yourself.
用于通过Slack、短信或应用内通知分享:
ts
const { data } = await authClient.organization.getInvitationURL({
  email: "newuser@example.com",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// 通过任意渠道分享data.url
注意:此端点不会调用
sendInvitationEmail
。请自行处理发送事宜。

Accepting Invitations

接受邀请

ts
await authClient.organization.acceptInvitation({
  invitationId: "invitation-id",
});
ts
await authClient.organization.acceptInvitation({
  invitationId: "邀请ID",
});

Invitation Configuration

邀请配置

ts
organization({
  invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days (default: 48 hours)
  invitationLimit: 100, // Max pending invitations per org
  cancelPendingInvitationsOnReInvite: true, // Cancel old invites when re-inviting
});
ts
organization({
  invitationExpiresIn: 60 * 60 * 24 * 7, // 7天(默认:48小时)
  invitationLimit: 100, // 每个组织最多待处理邀请数
  cancelPendingInvitationsOnReInvite: true, // 重新邀请时取消旧邀请
});

Roles & Permissions

角色与权限

The plugin provides role-based access control (RBAC) with three default roles:
RoleDescription
owner
Full access, can delete organization
admin
Can manage members, invitations, settings
member
Basic access to organization resources
该插件提供基于角色的访问控制(RBAC),包含三个默认角色:
角色描述
owner
完全访问权限,可删除组织
admin
可管理成员、邀请和设置
member
对组织资源的基础访问权限

Checking Permissions

检查权限

ts
const { data } = await authClient.organization.hasPermission({
  permission: "member:write",
});

if (data?.hasPermission) {
  // User can manage members
}
ts
const { data } = await authClient.organization.hasPermission({
  permission: "member:write",
});

if (data?.hasPermission) {
  // 用户可管理成员
}

Client-Side Permission Checks

客户端权限检查

For UI rendering without API calls:
ts
const canManageMembers = authClient.organization.checkRolePermission({
  role: "admin",
  permissions: ["member:write"],
});
Note: For dynamic access control, the client side role permission check will not work. Please use the
hasPermission
endpoint.
无需API调用即可用于UI渲染:
ts
const canManageMembers = authClient.organization.checkRolePermission({
  role: "admin",
  permissions: ["member:write"],
});
注意:对于动态访问控制,客户端角色权限检查将无法生效。请使用
hasPermission
端点。

Teams

团队

Teams allow grouping members within an organization.
团队允许在组织内对成员进行分组。

Enabling Teams

启用团队功能

ts
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
        teams: {
            enabled: true
        }
    }),
  ],
});
ts
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
        teams: {
            enabled: true
        }
    }),
  ],
});

Creating Teams

创建团队

ts
const { data } = await authClient.organization.createTeam({
  name: "Engineering",
});
ts
const { data } = await authClient.organization.createTeam({
  name: "技术部",
});

Managing Team Members

管理团队成员

ts
// Add a member to a team (must be org member first)
await authClient.organization.addTeamMember({
  teamId: "team-id",
  userId: "user-id",
});

// Remove from team (stays in org)
await authClient.organization.removeTeamMember({
  teamId: "team-id",
  userId: "user-id",
});
ts
// 将成员添加到团队(必须先成为组织成员)
await authClient.organization.addTeamMember({
  teamId: "团队ID",
  userId: "用户ID",
});

// 从团队移除成员(仍保留组织成员身份)
await authClient.organization.removeTeamMember({
  teamId: "团队ID",
  userId: "用户ID",
});

Active Teams

活跃团队

Similar to active organizations, set an active team for the session:
ts
await authClient.organization.setActiveTeam({
  teamId: "team-id",
});
与活跃组织类似,为会话设置活跃团队:
ts
await authClient.organization.setActiveTeam({
  teamId: "团队ID",
});

Team Limits

团队限制

ts
organization({
  teams: {
      maximumTeams: 20, // Max teams per org
      maximumMembersPerTeam: 50, // Max members per team
      allowRemovingAllTeams: false, // Prevent removing last team
  }
});
ts
organization({
  teams: {
      maximumTeams: 20, // 每个组织最多团队数
      maximumMembersPerTeam: 50, // 每个团队最多成员数
      allowRemovingAllTeams: false, // 禁止删除最后一个团队
  }
});

Dynamic Access Control

动态访问控制

For applications needing custom roles per organization at runtime, enable dynamic access control.
对于需要在运行时为每个组织自定义角色的应用,启用动态访问控制。

Enabling Dynamic Access Control

启用动态访问控制

ts
import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";

export const auth = betterAuth({
  plugins: [
    organization({
        dynamicAccessControl: {
            enabled: true
        }
    }),
  ],
});
ts
import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";

export const auth = betterAuth({
  plugins: [
    organization({
        dynamicAccessControl: {
            enabled: true
        }
    }),
  ],
});

Creating Custom Roles

创建自定义角色

ts
await authClient.organization.createRole({
  role: "moderator",
  permission: {
    member: ["read"],
    invitation: ["read"],
  },
});
ts
await authClient.organization.createRole({
  role: "moderator",
  permission: {
    member: ["read"],
    invitation: ["read"],
  },
});

Updating and Deleting Roles

更新和删除角色

ts
// Update role permissions
await authClient.organization.updateRole({
  roleId: "role-id",
  permission: {
    member: ["read", "write"],
  },
});

// Delete a custom role
await authClient.organization.deleteRole({
  roleId: "role-id",
});
Note: Pre-defined roles (owner, admin, member) cannot be deleted. Roles assigned to members cannot be deleted until members are reassigned.
ts
// 更新角色权限
await authClient.organization.updateRole({
  roleId: "角色ID",
  permission: {
    member: ["read", "write"],
  },
});

// 删除自定义角色
await authClient.organization.deleteRole({
  roleId: "角色ID",
});
注意:预定义角色(owner、admin、member)无法删除。已分配给成员的角色,需先重新分配成员角色才能删除。

Lifecycle Hooks

生命周期钩子

Execute custom logic at various points in the organization lifecycle:
ts
organization({
  hooks: {
    organization: {
      beforeCreate: async ({ data, user }) => {
        // Validate or modify data before creation
        return {
          data: {
            ...data,
            metadata: { ...data.metadata, createdBy: user.id },
          },
        };
      },
      afterCreate: async ({ organization, member }) => {
        // Post-creation logic (e.g., send welcome email, create default resources)
        await createDefaultResources(organization.id);
      },
      beforeDelete: async ({ organization }) => {
        // Cleanup before deletion
        await archiveOrganizationData(organization.id);
      },
    },
    member: {
      afterCreate: async ({ member, organization }) => {
        await notifyAdmins(organization.id, `New member joined`);
      },
    },
    invitation: {
      afterCreate: async ({ invitation, organization, inviter }) => {
        await logInvitation(invitation);
      },
    },
  },
});
在组织生命周期的各个阶段执行自定义逻辑:
ts
organization({
  hooks: {
    organization: {
      beforeCreate: async ({ data, user }) => {
        // 创建前验证或修改数据
        return {
          data: {
            ...data,
            metadata: { ...data.metadata, createdBy: user.id },
          },
        };
      },
      afterCreate: async ({ organization, member }) => {
        // 创建后逻辑(如发送欢迎邮件、创建默认资源)
        await createDefaultResources(organization.id);
      },
      beforeDelete: async ({ organization }) => {
        // 删除前清理
        await archiveOrganizationData(organization.id);
      },
    },
    member: {
      afterCreate: async ({ member, organization }) => {
        await notifyAdmins(organization.id, `新成员加入`);
      },
    },
    invitation: {
      afterCreate: async ({ invitation, organization, inviter }) => {
        await logInvitation(invitation);
      },
    },
  },
});

Schema Customization

Schema自定义

Customize table names, field names, and add additional fields:
ts
organization({
  schema: {
    organization: {
      modelName: "workspace", // Rename table
      fields: {
        name: "workspaceName", // Rename fields
      },
      additionalFields: {
        billingId: {
          type: "string",
          required: false,
        },
      },
    },
    member: {
      additionalFields: {
        department: {
          type: "string",
          required: false,
        },
        title: {
          type: "string",
          required: false,
        },
      },
    },
  },
});
自定义表名、字段名并添加额外字段:
ts
organization({
  schema: {
    organization: {
      modelName: "workspace", // 重命名表
      fields: {
        name: "workspaceName", // 重命名字段
      },
      additionalFields: {
        billingId: {
          type: "string",
          required: false,
        },
      },
    },
    member: {
      additionalFields: {
        department: {
          type: "string",
          required: false,
        },
        title: {
          type: "string",
          required: false,
        },
      },
    },
  },
});

Security Considerations

安全考量

Owner Protection

所有者保护

  • The last owner cannot be removed from an organization
  • The last owner cannot leave the organization
  • The owner role cannot be removed from the last owner
Always ensure ownership transfer before removing the current owner:
ts
// Transfer ownership first
await authClient.organization.updateMemberRole({
  memberId: "new-owner-member-id",
  role: "owner",
});

// Then the previous owner can be demoted or removed
  • 最后一位所有者无法从组织中移除
  • 最后一位所有者无法离开组织
  • 无法从最后一位所有者身上移除owner角色
移除当前所有者前,务必确保已转移所有权:
ts
// 先转移所有权
await authClient.organization.updateMemberRole({
  memberId: "新所有者成员ID",
  role: "owner",
});

// 然后可以降级或移除原所有者

Organization Deletion

组织删除

Deleting an organization removes all associated data (members, invitations, teams). Prevent accidental deletion:
ts
organization({
  disableOrganizationDeletion: true, // Disable via config
});
Or implement soft delete via hooks:
ts
organization({
  hooks: {
    organization: {
      beforeDelete: async ({ organization }) => {
        // Archive instead of delete
        await archiveOrganization(organization.id);
        throw new Error("Organization archived, not deleted");
      },
    },
  },
});
删除组织会移除所有关联数据(成员、邀请、团队)。防止意外删除:
ts
organization({
  disableOrganizationDeletion: true, // 通过配置禁用删除
});
或通过钩子实现软删除:
ts
organization({
  hooks: {
    organization: {
      beforeDelete: async ({ organization }) => {
        // 归档而非删除
        await archiveOrganization(organization.id);
        throw new Error("组织已归档,未删除");
      },
    },
  },
});

Invitation Security

邀请安全

  • Invitations expire after 48 hours by default
  • Only the invited email address can accept an invitation
  • Pending invitations can be cancelled by organization admins
  • 邀请默认48小时后过期
  • 只有受邀邮箱地址可接受邀请
  • 组织管理员可取消待处理邀请

Complete Configuration Example

完整配置示例

ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      // Organization limits
      allowUserToCreateOrganization: true,
      organizationLimit: 10,
      membershipLimit: 100,
      creatorRole: "owner",

      // Slugs
      defaultOrganizationIdField: "slug",

      // Invitations
      invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
      invitationLimit: 50,
      sendInvitationEmail: async (data) => {
        await sendEmail({
          to: data.email,
          subject: `Join ${data.organization.name}`,
          html: `<a href="https://app.com/invite/${data.invitation.id}">Accept</a>`,
        });
      },

      // Hooks
      hooks: {
        organization: {
          afterCreate: async ({ organization }) => {
            console.log(`Organization ${organization.name} created`);
          },
        },
      },
    }),
  ],
});
<!-- cross-ref:start -->
ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      // 组织限制
      allowUserToCreateOrganization: true,
      organizationLimit: 10,
      membershipLimit: 100,
      creatorRole: "owner",

      // Slug设置
      defaultOrganizationIdField: "slug",

      // 邀请设置
      invitationExpiresIn: 60 * 60 * 24 * 7, // 7天
      invitationLimit: 50,
      sendInvitationEmail: async (data) => {
        await sendEmail({
          to: data.email,
          subject: `加入${data.organization.name}`,
          html: `<a href="https://app.com/invite/${data.invitation.id}">接受邀请</a>`,
        });
      },

      // 钩子
      hooks: {
        organization: {
          afterCreate: async ({ organization }) => {
            console.log(`组织${organization.name}已创建`);
          },
        },
      },
    }),
  ],
});
<!-- cross-ref:start -->

See also (related skills — Better Auth family)

相关技能(Better Auth系列)

If your issue relates to:
  • Better Auth integration overview — check
    better-auth
    if appropriate.
  • best-practices guide — check
    better-auth-best-practices
    if appropriate.
  • create the auth layer (initial scaffolding) — check
    better-auth-create-auth
    if appropriate.
  • email/password, password reset, verification policies — check
    better-auth-email-password
    if appropriate.
  • explain a specific error code + provide fix — check
    better-auth-explain-error
    if appropriate.
  • OAuth/email/magic-link/social provider config — check
    better-auth-providers
    if appropriate.
  • rate limit, CSRF, trusted origins, secrets, OAuth security — check
    better-auth-security
    if appropriate.
  • twoFactor plugin enforcement — check
    better-auth-two-factor
    if appropriate.
  • wiring Better Auth into a Tauri desktop app — check
    better-auth-tauri-setup
    if appropriate.
  • Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check
    better-auth-tauri-pitfalls
    if appropriate.
  • reproduction guide for the better-auth-ui crash on Tauri v2 — check
    sawy-better-auth-ui-tauri-repro
    if appropriate.
<!-- cross-ref:end -->
如果你的问题涉及:
  • Better Auth集成概述 — 请查看
    better-auth
  • 最佳实践指南 — 请查看
    better-auth-best-practices
  • 创建认证层(初始脚手架) — 请查看
    better-auth-create-auth
  • 邮箱/密码、密码重置、验证策略 — 请查看
    better-auth-email-password
  • 解释特定错误码并提供修复方案 — 请查看
    better-auth-explain-error
  • OAuth/邮箱/魔法链接/社交登录提供商配置 — 请查看
    better-auth-providers
  • 速率限制、CSRF、可信来源、密钥、OAuth安全 — 请查看
    better-auth-security
  • twoFactor插件强制实施 — 请查看
    better-auth-two-factor
  • 在Tauri桌面应用中集成Better Auth — 请查看
    better-auth-tauri-setup
  • Tauri特定问题(Cookie、深度链接、macOS、404回调) — 请查看
    better-auth-tauri-pitfalls
  • Tauri v2上better-auth-ui崩溃的复现指南 — 请查看
    sawy-better-auth-ui-tauri-repro
<!-- cross-ref:end -->

Two-Factor (2FA)

双因素认证(2FA)

Setting Up Two-Factor Authentication

设置双因素认证

When adding 2FA to your application, configure the
twoFactor
plugin with your app name as the issuer. This name appears in authenticator apps when users scan the QR code.
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  appName: "My App", // Used as the default issuer for TOTP
  plugins: [
    twoFactor({
      issuer: "My App", // Optional: override the app name for 2FA specifically
    }),
  ],
});
Note: After adding the plugin, run
npx @better-auth/cli migrate
to add the required database fields and tables.
在应用中添加2FA时,配置
twoFactor
插件并将你的应用名称作为发行方。该名称会在用户扫描二维码时显示在认证器应用中。
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  appName: "我的应用", // 用作TOTP的默认发行方
  plugins: [
    twoFactor({
      issuer: "我的应用", // 可选:专门为2FA覆盖应用名称
    }),
  ],
});
注意:添加插件后,运行
npx @better-auth/cli migrate
以添加所需的数据库字段和表。

Client-Side Setup

客户端设置

Add the client plugin and configure the redirect behavior for 2FA verification:
ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      onTwoFactorRedirect() {
        window.location.href = "/2fa"; // Redirect to your 2FA verification page
      },
    }),
  ],
});
添加客户端插件并配置2FA验证的重定向行为:
ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      onTwoFactorRedirect() {
        window.location.href = "/2fa"; // 重定向到你的2FA验证页面
      },
    }),
  ],
});

Enabling 2FA for Users

为用户启用2FA

When a user enables 2FA, require their password for verification. The enable endpoint returns a TOTP URI for QR code generation and backup codes for account recovery.
ts
const enable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.enable({
    password,
  });

  if (data) {
    // data.totpURI - Use this to generate a QR code
    // data.backupCodes - Display these to the user for safekeeping
  }
};
Important: The
twoFactorEnabled
flag on the user is not set to
true
until the user successfully verifies their first TOTP code. This ensures users have properly configured their authenticator app before 2FA is fully active.
用户启用2FA时,要求其输入密码进行验证。启用端点会返回用于生成二维码的TOTP URI和用于账户恢复的备份码。
ts
const enable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.enable({
    password,
  });

  if (data) {
    // data.totpURI - 用于生成二维码
    // data.backupCodes - 显示给用户妥善保存
  }
};
重要:用户成功验证第一个TOTP代码后,用户的
twoFactorEnabled
标志才会设置为
true
。这确保用户在2FA完全激活前已正确配置认证器应用。

Skipping Initial Verification

跳过初始验证

If you want to enable 2FA immediately without requiring verification, set
skipVerificationOnEnable
:
ts
twoFactor({
  skipVerificationOnEnable: true, // Not recommended for most use cases
});
Note: This is generally not recommended as it doesn't confirm the user has successfully set up their authenticator app.
如果你想立即启用2FA而无需验证,设置
skipVerificationOnEnable
ts
twoFactor({
  skipVerificationOnEnable: true, // 大多数场景不推荐
});
注意:通常不建议这样做,因为无法确认用户已成功设置认证器应用。

TOTP (Authenticator App)

TOTP(认证器应用)

TOTP generates time-based codes using an authenticator app (Google Authenticator, Authy, etc.). Codes are valid for 30 seconds by default.
TOTP使用认证器应用(Google Authenticator、Authy等)生成基于时间的验证码。验证码默认30秒内有效。

Displaying the QR Code

显示二维码

Use the TOTP URI to generate a QR code for users to scan:
tsx
import QRCode from "react-qr-code";

const TotpSetup = ({ totpURI }: { totpURI: string }) => {
  return <QRCode value={totpURI} />;
};
使用TOTP URI生成二维码供用户扫描:
tsx
import QRCode from "react-qr-code";

const TotpSetup = ({ totpURI }: { totpURI: string }) => {
  return <QRCode value={totpURI} />;
};

Verifying TOTP Codes

验证TOTP代码

Better Auth accepts codes from one period before and one after the current time, accommodating minor clock differences between devices:
ts
const verifyTotp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyTotp({
    code,
    trustDevice: true, // Optional: remember this device for 30 days
  });
};
Better Auth接受当前时间段前后各一个时间段内的代码,以适应设备间的微小时钟差异:
ts
const verifyTotp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyTotp({
    code,
    trustDevice: true, // 可选:信任此设备30天
  });
};

TOTP Configuration Options

TOTP配置选项

ts
twoFactor({
  totpOptions: {
    digits: 6, // 6 or 8 digits (default: 6)
    period: 30, // Code validity period in seconds (default: 30)
  },
});
ts
twoFactor({
  totpOptions: {
    digits: 6, // 6位或8位(默认:6位)
    period: 30, // 验证码有效期(秒,默认:30)
  },
});

OTP (Email/SMS)

OTP(邮箱/短信)

OTP sends a one-time code to the user's email or phone. You must implement the
sendOTP
function to deliver codes.
OTP将一次性验证码发送到用户的邮箱或手机。你必须实现
sendOTP
函数来发送验证码。

Configuring OTP Delivery

配置OTP发送

ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    twoFactor({
      otpOptions: {
        sendOTP: async ({ user, otp }, ctx) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5, // Code validity in minutes (default: 3)
        digits: 6, // Number of digits (default: 6)
        allowedAttempts: 5, // Max verification attempts (default: 5)
      },
    }),
  ],
});
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    twoFactor({
      otpOptions: {
        sendOTP: async ({ user, otp }, ctx) => {
          await sendEmail({
            to: user.email,
            subject: "你的验证码",
            text: `你的验证码是:${otp}`,
          });
        },
        period: 5, // 验证码有效期(分钟,默认:3)
        digits: 6, // 验证码位数(默认:6)
        allowedAttempts: 5, // 最大验证尝试次数(默认:5)
      },
    }),
  ],
});

Sending and Verifying OTP

发送和验证OTP

ts
// Request an OTP to be sent
const sendOtp = async () => {
  const { data, error } = await authClient.twoFactor.sendOtp();
};

// Verify the OTP code
const verifyOtp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyOtp({
    code,
    trustDevice: true,
  });
};
ts
// 请求发送OTP
const sendOtp = async () => {
  const { data, error } = await authClient.twoFactor.sendOtp();
};

// 验证OTP代码
const verifyOtp = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyOtp({
    code,
    trustDevice: true,
  });
};

OTP Storage Security

OTP存储安全

Configure how OTP codes are stored in the database:
ts
twoFactor({
  otpOptions: {
    storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
  },
});
For custom encryption:
ts
twoFactor({
  otpOptions: {
    storeOTP: {
      encrypt: async (token) => myEncrypt(token),
      decrypt: async (token) => myDecrypt(token),
    },
  },
});
配置OTP代码在数据库中的存储方式:
ts
twoFactor({
  otpOptions: {
    storeOTP: "encrypted", // 选项:"plain", "encrypted", "hashed"
  },
});
如需自定义加密:
ts
twoFactor({
  otpOptions: {
    storeOTP: {
      encrypt: async (token) => myEncrypt(token),
      decrypt: async (token) => myDecrypt(token),
    },
  },
});

Backup Codes

备份码

Backup codes provide account recovery when users lose access to their authenticator app or phone. They are generated automatically when 2FA is enabled.
备份码在用户无法访问认证器应用或手机时提供账户恢复功能。启用2FA时会自动生成备份码。

Displaying Backup Codes

显示备份码

Always show backup codes to users when they enable 2FA:
tsx
const BackupCodes = ({ codes }: { codes: string[] }) => {
  return (
    <div>
      <p>Save these codes in a secure location:</p>
      <ul>
        {codes.map((code, i) => (
          <li key={i}>{code}</li>
        ))}
      </ul>
    </div>
  );
};
用户启用2FA时,务必向其显示备份码:
tsx
const BackupCodes = ({ codes }: { codes: string[] }) => {
  return (
    <div>
      <p>请将这些代码保存在安全位置:</p>
      <ul>
        {codes.map((code, i) => (
          <li key={i}>{code}</li>
        ))}
      </ul>
    </div>
  );
};

Regenerating Backup Codes

重新生成备份码

When users need new codes, regenerate them (this invalidates all previous codes):
ts
const regenerateBackupCodes = async (password: string) => {
  const { data, error } = await authClient.twoFactor.generateBackupCodes({
    password,
  });
  // data.backupCodes contains the new codes
};
用户需要新代码时,重新生成(这会使所有旧代码失效):
ts
const regenerateBackupCodes = async (password: string) => {
  const { data, error } = await authClient.twoFactor.generateBackupCodes({
    password,
  });
  // data.backupCodes包含新代码
};

Using Backup Codes for Recovery

使用备份码恢复账户

ts
const verifyBackupCode = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyBackupCode({
    code,
    trustDevice: true,
  });
};
Note: Each backup code can only be used once and is removed from the database after successful verification.
ts
const verifyBackupCode = async (code: string) => {
  const { data, error } = await authClient.twoFactor.verifyBackupCode({
    code,
    trustDevice: true,
  });
};
注意:每个备份码只能使用一次,验证成功后会从数据库中移除。

Backup Code Configuration

备份码配置

ts
twoFactor({
  backupCodeOptions: {
    amount: 10, // Number of codes to generate (default: 10)
    length: 10, // Length of each code (default: 10)
    storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
  },
});
ts
twoFactor({
  backupCodeOptions: {
    amount: 10, // 生成的代码数量(默认:10)
    length: 10, // 每个代码的长度(默认:10)
    storeBackupCodes: "encrypted", // 选项:"plain", "encrypted"
  },
});

Handling 2FA During Sign-In

登录时处理2FA

When a user with 2FA enabled signs in, the response includes
twoFactorRedirect: true
:
ts
const signIn = async (email: string, password: string) => {
  const { data, error } = await authClient.signIn.email(
    {
      email,
      password,
    },
    {
      onSuccess(context) {
        if (context.data.twoFactorRedirect) {
          // Redirect to 2FA verification page
          window.location.href = "/2fa";
        }
      },
    }
  );
};
启用2FA的用户登录时,响应会包含
twoFactorRedirect: true
ts
const signIn = async (email: string, password: string) => {
  const { data, error } = await authClient.signIn.email(
    {
      email,
      password,
    },
    {
      onSuccess(context) {
        if (context.data.twoFactorRedirect) {
          // 重定向到2FA验证页面
          window.location.href = "/2fa";
        }
      },
    }
  );
};

Server-Side 2FA Detection

服务器端2FA检测

When using
auth.api.signInEmail
on the server, check for 2FA redirect:
ts
const response = await auth.api.signInEmail({
  body: {
    email: "user@example.com",
    password: "password",
  },
});

if ("twoFactorRedirect" in response) {
  // Handle 2FA verification
}
在服务器端使用
auth.api.signInEmail
时,检查是否需要2FA重定向:
ts
const response = await auth.api.signInEmail({
  body: {
    email: "user@example.com",
    password: "password",
  },
});

if ("twoFactorRedirect" in response) {
  // 处理2FA验证
}

Trusted Devices

可信设备

Trusted devices allow users to skip 2FA verification on subsequent sign-ins for a configurable period.
可信设备允许用户在后续登录时跳过2FA验证,有效期可配置。

Enabling Trust on Verification

验证时启用信任

Pass
trustDevice: true
when verifying 2FA:
ts
await authClient.twoFactor.verifyTotp({
  code: "123456",
  trustDevice: true,
});
验证2FA时传入
trustDevice: true
ts
await authClient.twoFactor.verifyTotp({
  code: "123456",
  trustDevice: true,
});

Configuring Trust Duration

配置信任时长

ts
twoFactor({
  trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days in seconds (default)
});
Note: The trust period refreshes on each successful sign-in within the trust window.
ts
twoFactor({
  trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30天(秒,默认值)
});
注意:在信任窗口内每次成功登录都会刷新信任周期。

Security Considerations

安全考量

Session Management

会话管理

During the 2FA flow:
  1. User signs in with credentials
  2. Session cookie is removed (not yet authenticated)
  3. A temporary two-factor cookie is set (default: 10-minute expiration)
  4. User verifies via TOTP, OTP, or backup code
  5. Session cookie is created upon successful verification
Configure the two-factor cookie expiration:
ts
twoFactor({
  twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});
2FA流程中:
  1. 用户使用凭证登录
  2. 会话Cookie被移除(尚未完成认证)
  3. 设置临时双因素Cookie(默认:10分钟过期)
  4. 用户通过TOTP、OTP或备份码验证
  5. 验证成功后创建会话Cookie
配置双因素Cookie过期时间:
ts
twoFactor({
  twoFactorCookieMaxAge: 600, // 10分钟(秒,默认值)
});

Rate Limiting

速率限制

Better Auth applies built-in rate limiting to all 2FA endpoints (3 requests per 10 seconds). For OTP verification, additional attempt limiting is applied:
ts
twoFactor({
  otpOptions: {
    allowedAttempts: 5, // Max attempts per OTP code (default: 5)
  },
});
Better Auth对所有2FA端点应用内置速率限制(每10秒3次请求)。对于OTP验证,还会应用额外的尝试次数限制:
ts
twoFactor({
  otpOptions: {
    allowedAttempts: 5, // 每个OTP代码的最大尝试次数(默认:5)
  },
});

Encryption at Rest

静态加密

  • TOTP secrets are encrypted using symmetric encryption with your auth secret
  • Backup codes are stored encrypted by default
  • OTP codes can be configured for plain, encrypted, or hashed storage
  • TOTP密钥使用你的认证密钥通过对称加密存储
  • 备份码默认加密存储
  • OTP代码可配置为明文、加密或哈希存储

Constant-Time Comparison

恒定时间比较

Better Auth uses constant-time comparison for OTP verification to prevent timing attacks.
Better Auth使用恒定时间比较进行OTP验证,以防止时序攻击。

Credential Account Requirement

凭证账户要求

Two-factor authentication can only be enabled for credential (email/password) accounts. For social accounts, it's assumed the provider already handles 2FA.
双因素认证仅可用于凭证(邮箱/密码)账户。对于社交账户,假设提供商已处理2FA。

Disabling 2FA

禁用2FA

Allow users to disable 2FA with password confirmation:
ts
const disable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.disable({
    password,
  });
};
Note: When 2FA is disabled, trusted device records are revoked.
允许用户通过密码确认禁用2FA:
ts
const disable2FA = async (password: string) => {
  const { data, error } = await authClient.twoFactor.disable({
    password,
  });
};
注意:禁用2FA时,可信设备记录会被撤销。

Complete Configuration Example

完整配置示例

ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  appName: "My App",
  plugins: [
    twoFactor({
      // TOTP settings
      issuer: "My App",
      totpOptions: {
        digits: 6,
        period: 30,
      },
      // OTP settings
      otpOptions: {
        sendOTP: async ({ user, otp }) => {
          await sendEmail({
            to: user.email,
            subject: "Your verification code",
            text: `Your code is: ${otp}`,
          });
        },
        period: 5,
        allowedAttempts: 5,
        storeOTP: "encrypted",
      },
      // Backup code settings
      backupCodeOptions: {
        amount: 10,
        length: 10,
        storeBackupCodes: "encrypted",
      },
      // Session settings
      twoFactorCookieMaxAge: 600, // 10 minutes
      trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days
    }),
  ],
});
<!-- cross-ref:start -->
ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  appName: "我的应用",
  plugins: [
    twoFactor({
      // TOTP设置
      issuer: "我的应用",
      totpOptions: {
        digits: 6,
        period: 30,
      },
      // OTP设置
      otpOptions: {
        sendOTP: async ({ user, otp }) => {
          await sendEmail({
            to: user.email,
            subject: "你的验证码",
            text: `你的验证码是:${otp}`,
          });
        },
        period: 5,
        allowedAttempts: 5,
        storeOTP: "encrypted",
      },
      // 备份码设置
      backupCodeOptions: {
        amount: 10,
        length: 10,
        storeBackupCodes: "encrypted",
      },
      // 会话设置
      twoFactorCookieMaxAge: 600, // 10分钟
      trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30天
    }),
  ],
});
<!-- cross-ref:start -->

See also (related skills — Better Auth family)

相关技能(Better Auth系列)

If your issue relates to:
  • Better Auth integration overview — check
    better-auth
    if appropriate.
  • best-practices guide — check
    better-auth-best-practices
    if appropriate.
  • create the auth layer (initial scaffolding) — check
    better-auth-create-auth
    if appropriate.
  • email/password, password reset, verification policies — check
    better-auth-email-password
    if appropriate.
  • explain a specific error code + provide fix — check
    better-auth-explain-error
    if appropriate.
  • organization/team plugin — check
    better-auth-organization
    if appropriate.
  • OAuth/email/magic-link/social provider config — check
    better-auth-providers
    if appropriate.
  • rate limit, CSRF, trusted origins, secrets, OAuth security — check
    better-auth-security
    if appropriate.
  • wiring Better Auth into a Tauri desktop app — check
    better-auth-tauri-setup
    if appropriate.
  • Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check
    better-auth-tauri-pitfalls
    if appropriate.
  • reproduction guide for the better-auth-ui crash on Tauri v2 — check
    sawy-better-auth-ui-tauri-repro
    if appropriate.
<!-- cross-ref:end -->
如果你的问题涉及:
  • Better Auth集成概述 — 请查看
    better-auth
  • 最佳实践指南 — 请查看
    better-auth-best-practices
  • 创建认证层(初始脚手架) — 请查看
    better-auth-create-auth
  • 邮箱/密码、密码重置、验证策略 — 请查看
    better-auth-email-password
  • 解释特定错误码并提供修复方案 — 请查看
    better-auth-explain-error
  • 组织/团队插件 — 请查看
    better-auth-organization
  • OAuth/邮箱/魔法链接/社交登录提供商配置 — 请查看
    better-auth-providers
  • 速率限制、CSRF、可信来源、密钥、OAuth安全 — 请查看
    better-auth-security
  • 在Tauri桌面应用中集成Better Auth — 请查看
    better-auth-tauri-setup
  • Tauri特定问题(Cookie、深度链接、macOS、404回调) — 请查看
    better-auth-tauri-pitfalls
  • Tauri v2上better-auth-ui崩溃的复现指南 — 请查看
    sawy-better-auth-ui-tauri-repro
<!-- cross-ref:end -->