email-and-password-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Email Verification Setup

邮箱验证设置

When enabling email/password authentication, configure
emailVerification.sendVerificationEmail
to verify user email addresses. This helps prevent fake sign-ups and ensures users have access to the email they registered with.
ts
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function

export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url, token }, request) => {
      await sendEmail({
        to: user.email,
        subject: "Verify your email address",
        text: `Click the link to verify your email: ${url}`,
      });
    },
  },
});
Note: The
url
parameter contains the full verification link. The
token
is available if you need to build a custom verification URL.
启用邮箱/密码认证时,请配置
emailVerification.sendVerificationEmail
以验证用户邮箱地址。这有助于防止虚假注册,并确保用户能够访问其注册时使用的邮箱。
ts
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function

export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url, token }, request) => {
      await sendEmail({
        to: user.email,
        subject: "Verify your email address",
        text: `Click the link to verify your email: ${url}`,
      });
    },
  },
});
注意
url
参数包含完整的验证链接。如果您需要构建自定义验证URL,可以使用
token
参数。

Requiring Email Verification

强制邮箱验证

For stricter security, enable
emailAndPassword.requireEmailVerification
to block sign-in until the user verifies their email. When enabled, unverified users will receive a new verification email on each sign-in attempt.
ts
export const auth = betterAuth({
  emailAndPassword: {
    requireEmailVerification: true,
  },
});
Note: This requires
sendVerificationEmail
to be configured and only applies to email/password sign-ins.
为提升安全性,可启用
emailAndPassword.requireEmailVerification
以阻止未验证邮箱的用户登录。启用后,未验证用户每次尝试登录时都会收到一封新的验证邮件。
ts
export const auth = betterAuth({
  emailAndPassword: {
    requireEmailVerification: true,
  },
});
注意:此配置要求已设置
sendVerificationEmail
,且仅适用于邮箱/密码登录方式。

Client side validation

客户端验证

While Better Auth validates inputs server-side, implementing client-side validation is still recommended for two key reasons:
  1. Improved UX: Users receive immediate feedback when inputs don't meet requirements, rather than waiting for a server round-trip.
  2. Reduced server load: Invalid requests are caught early, minimizing unnecessary network traffic to your auth server.
尽管Better Auth会在服务端验证输入,但仍建议实现客户端验证,主要有两个原因:
  1. 提升用户体验:当输入不符合要求时,用户可立即收到反馈,无需等待服务器往返响应。
  2. 降低服务器负载:提前拦截无效请求,减少发送至认证服务器的不必要网络流量。

Callback URLs

回调URL

Always use absolute URLs (including the origin) for callback URLs in sign-up and sign-in requests. This prevents Better Auth from needing to infer the origin, which can cause issues when your backend and frontend are on different domains.
ts
const { data, error } = await authClient.signUp.email({
  callbackURL: "https://example.com/callback", // absolute URL with origin
});
在注册和登录请求中,回调URL请始终使用绝对URL(包含源地址)。这可避免Better Auth需要推断源地址,防止后端与前端位于不同域名时出现问题。
ts
const { data, error } = await authClient.signUp.email({
  callbackURL: "https://example.com/callback", // absolute URL with origin
});

Password Reset Flows

密码重置流程

Password reset flows are essential to any email/password system, we recommend setting this up.
To allow users to reset a password first you need to provide
sendResetPassword
function to the email and password authenticator.
ts
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    // Custom email sending function to send reset-password email
    sendResetPassword: async ({ user, url, token }, request) => {
      void sendEmail({
        to: user.email,
        subject: "Reset your password",
        text: `Click the link to reset your password: ${url}`,
      });
    },
    // Optional event hook
    onPasswordReset: async ({ user }, request) => {
      // your logic here
      console.log(`Password for user ${user.email} has been reset.`);
    },
  },
});
密码重置流程是任何邮箱/密码系统的必备功能,我们建议配置该功能。
要允许用户重置密码,首先需要为邮箱密码认证器提供
sendResetPassword
函数。
ts
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    // Custom email sending function to send reset-password email
    sendResetPassword: async ({ user, url, token }, request) => {
      void sendEmail({
        to: user.email,
        subject: "Reset your password",
        text: `Click the link to reset your password: ${url}`,
      });
    },
    // Optional event hook
    onPasswordReset: async ({ user }, request) => {
      // your logic here
      console.log(`Password for user ${user.email} has been reset.`);
    },
  },
});

Security considerations

安全注意事项

Better Auth implements several security measures in the password reset flow:
Better Auth在密码重置流程中实现了多项安全措施:

Timing attack prevention

防止时序攻击

  • Background email sending: Better Auth uses
    runInBackgroundOrAwait
    internally to send reset emails without blocking the response. This prevents attackers from measuring response times to determine if an email exists.
  • Dummy operations on invalid requests: When a user is not found, Better Auth still performs token generation and a database lookup (with a dummy value) to maintain consistent response times.
  • Constant response message: The API always returns
    "If this email exists in our system, check your email for the reset link"
    regardless of whether the user exists.
On serverless platforms, configure a background task handler to ensure emails are sent reliably:
ts
export const auth = betterAuth({
  advanced: {
    backgroundTasks: {
      handler: (promise) => {
        // Use platform-specific methods like waitUntil
        waitUntil(promise);
      },
    },
  },
});
  • 后台发送邮件:Better Auth内部使用
    runInBackgroundOrAwait
    发送重置邮件,不会阻塞响应。这可防止攻击者通过测量响应时间判断邮箱是否存在。
  • 无效请求的虚拟操作:当未找到用户时,Better Auth仍会执行令牌生成和数据库查询(使用虚拟值),以保持一致的响应时间。
  • 统一响应消息:无论用户是否存在,API始终返回
    "If this email exists in our system, check your email for the reset link"
在无服务器平台上,请配置后台任务处理程序以确保邮件可靠发送:
ts
export const auth = betterAuth({
  advanced: {
    backgroundTasks: {
      handler: (promise) => {
        // Use platform-specific methods like waitUntil
        waitUntil(promise);
      },
    },
  },
});

Token security

令牌安全

  • Cryptographically random tokens: Reset tokens are generated using
    generateId(24)
    , producing a 24-character alphanumeric string (a-z, A-Z, 0-9) with high entropy.
  • Token expiration: Tokens expire after 1 hour by default. Configure with
    resetPasswordTokenExpiresIn
    (in seconds):
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    resetPasswordTokenExpiresIn: 60 * 30, // 30 minutes
  },
});
  • Single-use tokens: Tokens are deleted immediately after successful password reset, preventing reuse.
  • 加密随机令牌:重置令牌使用
    generateId(24)
    生成,产生一个24字符的字母数字字符串(a-z, A-Z, 0-9),具有高熵值。
  • 令牌过期:令牌默认1小时后过期。可通过
    resetPasswordTokenExpiresIn
    (单位:秒)配置:
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    resetPasswordTokenExpiresIn: 60 * 30, // 30 minutes
  },
});
  • 一次性令牌:成功重置密码后,令牌会立即删除,防止重复使用。

Session revocation

会话吊销

Enable
revokeSessionsOnPasswordReset
to invalidate all existing sessions when a password is reset. This ensures that if an attacker has an active session, it will be terminated:
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    revokeSessionsOnPasswordReset: true,
  },
});
启用
revokeSessionsOnPasswordReset
可在密码重置时使所有现有会话失效。这确保如果攻击者拥有活跃会话,该会话将被终止:
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    revokeSessionsOnPasswordReset: true,
  },
});

Redirect URL validation

重定向URL验证

The
redirectTo
parameter is validated against your
trustedOrigins
configuration to prevent open redirect attacks. Malicious redirect URLs will be rejected with a 403 error.
redirectTo
参数会根据您的
trustedOrigins
配置进行验证,以防止开放重定向攻击。恶意重定向URL将被拒绝并返回403错误。

Password requirements

密码要求

During password reset, the new password must meet length requirements:
  • Minimum: 8 characters (default), configurable via
    minPasswordLength
  • Maximum: 128 characters (default), configurable via
    maxPasswordLength
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 12,
    maxPasswordLength: 256,
  },
});
重置密码时,新密码必须满足长度要求:
  • 最小值:8个字符(默认),可通过
    minPasswordLength
    配置
  • 最大值:128个字符(默认),可通过
    maxPasswordLength
    配置
ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 12,
    maxPasswordLength: 256,
  },
});

Sending the password reset

发送密码重置请求

Once the password reset configurations are set-up, you can now call the
requestPasswordReset
function to send reset password link to user. If the user exists, it will trigger the
sendResetPassword
function you provided in the auth config.
ts
const data = await auth.api.requestPasswordReset({
  body: {
    email: "john.doe@example.com", // required
    redirectTo: "https://example.com/reset-password",
  },
});
Or authClient:
ts
const { data, error } = await authClient.requestPasswordReset({
  email: "john.doe@example.com", // required
  redirectTo: "https://example.com/reset-password",
});
Note: While the
email
is required, we also recommend configuring the
redirectTo
for a smooother user experience.
完成密码重置配置后,您可以调用
requestPasswordReset
函数向用户发送密码重置链接。如果用户存在,将触发您在认证配置中提供的
sendResetPassword
函数。
ts
const data = await auth.api.requestPasswordReset({
  body: {
    email: "john.doe@example.com", // required
    redirectTo: "https://example.com/reset-password",
  },
});
或使用authClient:
ts
const { data, error } = await authClient.requestPasswordReset({
  email: "john.doe@example.com", // required
  redirectTo: "https://example.com/reset-password",
});
注意
email
为必填参数,我们还建议配置
redirectTo
以获得更流畅的用户体验。

Password Hashing

密码哈希

Better Auth uses
scrypt
by default for password hashing. This is a solid choice because:
  • It's designed to be slow and memory-intensive, making brute-force attacks costly
  • It's natively supported by Node.js (no external dependencies)
  • OWASP recommends it when Argon2id isn't available
Better Auth默认使用
scrypt
进行密码哈希。这是一个可靠的选择,原因如下:
  • 设计为慢且占用大量内存的算法,使暴力破解攻击成本高昂
  • 原生支持Node.js(无需外部依赖)
  • 当Argon2id不可用时,OWASP推荐使用它

Custom Hashing Algorithm

自定义哈希算法

To use a different algorithm (e.g., Argon2id), provide custom
hash
and
verify
functions in the
emailAndPassword.password
configuration:
ts
import { betterAuth } from "better-auth";
import { hash, verify, type Options } from "@node-rs/argon2";

const argon2Options: Options = {
  memoryCost: 65536, // 64 MiB
  timeCost: 3, // 3 iterations
  parallelism: 4, // 4 parallel lanes
  outputLen: 32, // 32 byte output
  algorithm: 2, // Argon2id variant
};

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    password: {
      hash: (password) => hash(password, argon2Options),
      verify: ({ password, hash: storedHash }) =>
        verify(storedHash, password, argon2Options),
    },
  },
});
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.
要使用其他算法(如Argon2id),请在
emailAndPassword.password
配置中提供自定义的
hash
verify
函数:
ts
import { betterAuth } from "better-auth";
import { hash, verify, type Options } from "@node-rs/argon2";

const argon2Options: Options = {
  memoryCost: 65536, // 64 MiB
  timeCost: 3, // 3 iterations
  parallelism: 4, // 4 parallel lanes
  outputLen: 32, // 32 byte output
  algorithm: 2, // Argon2id variant
};

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    password: {
      hash: (password) => hash(password, argon2Options),
      verify: ({ password, hash: storedHash }) =>
        verify(storedHash, password, argon2Options),
    },
  },
});
注意:如果在现有系统中切换哈希算法,使用旧算法哈希密码的用户将无法登录。如有需要,请制定迁移策略。