better-auth-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBetter 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 skill has the integration story; covers cross-cutting hardening; this one covers feature-by-feature best practices.
better-authbetter-auth-security| Feature | Section |
|---|---|
| Email + password (verification, reset, hashing, policies) | Email/Password |
| Organizations, teams, RBAC | Organization plugin |
| Two-factor authentication (TOTP, backup codes, recovery) | Two-Factor (2FA) |
For Tauri-specific integration, see . For OAuth/social providers, see . For the broader integration overview, see .
better-auth-tauri-setupbetter-auth-providersbetter-auth本指南整合了Better Auth流量最高的三大功能领域。选择与你正在实现的功能匹配的章节——父级技能包含集成说明;涵盖跨领域安全强化;本指南则针对每个功能提供最佳实践。
better-authbetter-auth-security| 功能 | 章节 |
|---|---|
| 邮箱+密码(验证、重置、哈希、策略) | 邮箱/密码 |
| 组织、团队、RBAC | 组织插件 |
| 双因素认证(TOTP、备份码、恢复) | 双因素认证(2FA) |
针对Tauri的特定集成,请查看。关于OAuth/社交登录提供商,请查看。如需更全面的集成概述,请查看。
better-auth-tauri-setupbetter-auth-providersbetter-authEmail/Password
邮箱/密码
Quick Start
快速开始
- Enable email/password:
emailAndPassword: { enabled: true } - Configure
emailVerification.sendVerificationEmail - Add for password reset flows
sendResetPassword - Run
npx @better-auth/cli@latest migrate - Verify: attempt sign-up and confirm verification email triggers
- 启用邮箱/密码认证:
emailAndPassword: { enabled: true } - 配置
emailVerification.sendVerificationEmail - 添加以支持密码重置流程
sendResetPassword - 运行
npx @better-auth/cli@latest migrate - 验证:尝试注册并确认验证邮件是否触发
Email Verification Setup
邮箱验证设置
Configure to verify user email addresses.
emailVerification.sendVerificationEmail配置以验证用户邮箱地址。
emailVerification.sendVerificationEmailRequiring Email Verification
强制邮箱验证
For stricter security, enable to block sign-in until the user verifies their email.
emailAndPassword.requireEmailVerification为提升安全性,启用,阻止用户在验证邮箱前登录。
emailAndPassword.requireEmailVerificationClient 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 in the email and password config to enable password resets.
sendResetPassword在邮箱和密码配置中提供以启用密码重置功能。
sendResetPasswordSecurity 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内置防护措施:后台发送邮件(防止时序攻击)、对无效请求执行虚拟操作、无论用户是否存在均返回一致响应信息。
在无服务器平台上,使用配置后台任务处理程序。
waitUntilToken Security
令牌安全
Tokens expire after 1 hour by default. Configure with (in seconds). Tokens are single-use.
resetPasswordTokenExpiresIn令牌默认1小时后过期。可通过(单位:秒)进行配置。令牌为一次性使用。
resetPasswordTokenExpiresInSession Revocation
会话吊销
Enable to invalidate all existing sessions on password reset.
revokeSessionsOnPasswordReset启用,在密码重置时使所有现有会话失效。
revokeSessionsOnPasswordResetPassword Requirements
密码要求
Password length limits configurable via and .
minPasswordLengthmaxPasswordLength可通过和配置密码长度限制。
minPasswordLengthmaxPasswordLengthPassword Hashing
密码哈希
Default: (Node.js native, no external dependencies).
scrypt默认算法:(Node.js原生算法,无外部依赖)。
scryptCustom Hashing Algorithm
自定义哈希算法
To use Argon2id or another algorithm, provide custom and functions.
hashverifyNote: 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或其他算法,请提供自定义和函数。
hashverify注意:如果在现有系统中切换哈希算法,使用旧算法哈希密码的用户将无法登录。如有需要,请规划迁移策略。
<!-- cross-ref:start -->See also (related skills — Better Auth family)
相关技能(Better Auth系列)
If your issue relates to:
- Better Auth integration overview — check if appropriate.
better-auth - best-practices guide — check if appropriate.
better-auth-best-practices - create the auth layer (initial scaffolding) — check if appropriate.
better-auth-create-auth - explain a specific error code + provide fix — check if appropriate.
better-auth-explain-error - organization/team plugin — check if appropriate.
better-auth-organization - OAuth/email/magic-link/social provider config — check if appropriate.
better-auth-providers - rate limit, CSRF, trusted origins, secrets, OAuth security — check if appropriate.
better-auth-security - twoFactor plugin enforcement — check if appropriate.
better-auth-two-factor - wiring Better Auth into a Tauri desktop app — check if appropriate.
better-auth-tauri-setup - Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check if appropriate.
better-auth-tauri-pitfalls - reproduction guide for the better-auth-ui crash on Tauri v2 — check if appropriate.
sawy-better-auth-ui-tauri-repro
如果你的问题涉及:
- 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
Organization plugin
组织插件
Setting Up Organizations
设置组织功能
When adding organizations to your application, configure the plugin with appropriate limits and permissions.
organizationts
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 to add the required database tables.
npx @better-auth/cli migrate在应用中添加组织功能时,配置插件并设置合适的限制和权限。
organizationts
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 migrateClient-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 role.
ownerts
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" },
});
};组织是用于分组用户的顶级实体。创建时,创建者将自动被分配角色。
ownerts
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 parameter cannot be used alongside session headers.
userId管理员可以为其他用户创建组织(仅支持服务器端):
ts
await auth.api.createOrganization({
body: {
name: "客户组织",
slug: "client-org",
userId: "将成为所有者的用户ID", // `userId`为必填项
},
});注意:参数不能与会话头一起使用。
userIdActive 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 is not provided:
organizationIdts
// 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,
});
};当未提供时,许多端点会自动使用活跃组织:
organizationIdts
// 这些操作会自动使用活跃组织
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.teamsMembers
成员
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 channelNote: This endpoint does not call . Handle delivery yourself.
sendInvitationEmail用于通过Slack、短信或应用内通知分享:
ts
const { data } = await authClient.organization.getInvitationURL({
email: "newuser@example.com",
role: "member",
callbackURL: "https://yourapp.com/dashboard",
});
// 通过任意渠道分享data.url注意:此端点不会调用。请自行处理发送事宜。
sendInvitationEmailAccepting 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:
| Role | Description |
|---|---|
| Full access, can delete organization |
| Can manage members, invitations, settings |
| Basic access to organization resources |
该插件提供基于角色的访问控制(RBAC),包含三个默认角色:
| 角色 | 描述 |
|---|---|
| 完全访问权限,可删除组织 |
| 可管理成员、邀请和设置 |
| 对组织资源的基础访问权限 |
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 endpoint.
hasPermission无需API调用即可用于UI渲染:
ts
const canManageMembers = authClient.organization.checkRolePermission({
role: "admin",
permissions: ["member:write"],
});注意:对于动态访问控制,客户端角色权限检查将无法生效。请使用端点。
hasPermissionTeams
团队
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`);
},
},
},
}),
],
});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}已创建`);
},
},
},
}),
],
});See also (related skills — Better Auth family)
相关技能(Better Auth系列)
If your issue relates to:
- Better Auth integration overview — check if appropriate.
better-auth - best-practices guide — check if appropriate.
better-auth-best-practices - create the auth layer (initial scaffolding) — check if appropriate.
better-auth-create-auth - email/password, password reset, verification policies — check if appropriate.
better-auth-email-password - explain a specific error code + provide fix — check if appropriate.
better-auth-explain-error - OAuth/email/magic-link/social provider config — check if appropriate.
better-auth-providers - rate limit, CSRF, trusted origins, secrets, OAuth security — check if appropriate.
better-auth-security - twoFactor plugin enforcement — check if appropriate.
better-auth-two-factor - wiring Better Auth into a Tauri desktop app — check if appropriate.
better-auth-tauri-setup - Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check if appropriate.
better-auth-tauri-pitfalls - reproduction guide for the better-auth-ui crash on Tauri v2 — check if appropriate.
sawy-better-auth-ui-tauri-repro
如果你的问题涉及:
- 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
Two-Factor (2FA)
双因素认证(2FA)
Setting Up Two-Factor Authentication
设置双因素认证
When adding 2FA to your application, configure the plugin with your app name as the issuer. This name appears in authenticator apps when users scan the QR code.
twoFactorts
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 to add the required database fields and tables.
npx @better-auth/cli migrate在应用中添加2FA时,配置插件并将你的应用名称作为发行方。该名称会在用户扫描二维码时显示在认证器应用中。
twoFactorts
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 migrateClient-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 flag on the user is not set to until the user successfully verifies their first TOTP code. This ensures users have properly configured their authenticator app before 2FA is fully active.
twoFactorEnabledtrue用户启用2FA时,要求其输入密码进行验证。启用端点会返回用于生成二维码的TOTP URI和用于账户恢复的备份码。
ts
const enable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.enable({
password,
});
if (data) {
// data.totpURI - 用于生成二维码
// data.backupCodes - 显示给用户妥善保存
}
};重要:用户成功验证第一个TOTP代码后,用户的标志才会设置为。这确保用户在2FA完全激活前已正确配置认证器应用。
twoFactorEnabledtrueSkipping Initial Verification
跳过初始验证
If you want to enable 2FA immediately without requiring verification, set :
skipVerificationOnEnablets
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而无需验证,设置:
skipVerificationOnEnablets
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 function to deliver codes.
sendOTPOTP将一次性验证码发送到用户的邮箱或手机。你必须实现函数来发送验证码。
sendOTPConfiguring 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: truets
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: truets
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 on the server, check for 2FA redirect:
auth.api.signInEmailts
const response = await auth.api.signInEmail({
body: {
email: "user@example.com",
password: "password",
},
});
if ("twoFactorRedirect" in response) {
// Handle 2FA verification
}在服务器端使用时,检查是否需要2FA重定向:
auth.api.signInEmailts
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 when verifying 2FA:
trustDevice: truets
await authClient.twoFactor.verifyTotp({
code: "123456",
trustDevice: true,
});验证2FA时传入:
trustDevice: truets
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:
- User signs in with credentials
- Session cookie is removed (not yet authenticated)
- A temporary two-factor cookie is set (default: 10-minute expiration)
- User verifies via TOTP, OTP, or backup code
- Session cookie is created upon successful verification
Configure the two-factor cookie expiration:
ts
twoFactor({
twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});2FA流程中:
- 用户使用凭证登录
- 会话Cookie被移除(尚未完成认证)
- 设置临时双因素Cookie(默认:10分钟过期)
- 用户通过TOTP、OTP或备份码验证
- 验证成功后创建会话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
}),
],
});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天
}),
],
});See also (related skills — Better Auth family)
相关技能(Better Auth系列)
If your issue relates to:
- Better Auth integration overview — check if appropriate.
better-auth - best-practices guide — check if appropriate.
better-auth-best-practices - create the auth layer (initial scaffolding) — check if appropriate.
better-auth-create-auth - email/password, password reset, verification policies — check if appropriate.
better-auth-email-password - explain a specific error code + provide fix — check if appropriate.
better-auth-explain-error - organization/team plugin — check if appropriate.
better-auth-organization - OAuth/email/magic-link/social provider config — check if appropriate.
better-auth-providers - rate limit, CSRF, trusted origins, secrets, OAuth security — check if appropriate.
better-auth-security - wiring Better Auth into a Tauri desktop app — check if appropriate.
better-auth-tauri-setup - Tauri-specific gotchas (cookies, deep links, macOS, 404 callbacks) — check if appropriate.
better-auth-tauri-pitfalls - reproduction guide for the better-auth-ui crash on Tauri v2 — check if appropriate.
sawy-better-auth-ui-tauri-repro
如果你的问题涉及:
- 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