速率限制

了解如何在 Better Auth 中配置速率限制,包括 IP 地址检测、IPv6 支持、自定义速率限制时间窗口、存储后端、错误处理和按端点的规则。

Better Auth 包含内置的速率限制器,以帮助管理流量和防止滥用。默认情况下,在生产模式下,速率限制器设置为:

  • 窗口:60 秒
  • 最大请求数:100 次请求

使用 auth.api 进行的服务器端请求不受速率限制影响。速率限制仅适用于客户端发起的请求。

您可以通过将 rateLimit 对象传递给 betterAuth 函数轻松自定义这些设置。

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

export const auth = betterAuth({
    rateLimit: {
        window: 10, // 时间窗口,单位秒
        max: 100, // 窗口内最大请求数
    },
})

开发模式下默认禁用速率限制。若想启用,请将 enabled 设置为 true

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

export const auth = betterAuth({
    rateLimit: {
        enabled: true,
        //...其他选项
    },
})

除了默认设置,Better Auth 还为特定路径提供自定义规则。例如:

  • /sign-in/email:在 10 秒内限制为 3 次请求。

此外,插件也会为特定路径定义自定义规则。例如,twoFactor 插件具有自定义规则:

  • /two-factor/verify:在 10 秒内限制为 3 次请求。

这些自定义规则确保敏感操作受到更严格的限制保护。

配置速率限制

连接 IP 地址

速率限制通过连接的 IP 地址来跟踪用户发起的请求数量。默认检查的请求头为 x-forwarded-for,这在生产环境中常用。如果您使用其他请求头来跟踪用户的 IP 地址,则需要指定它。

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

export const auth = betterAuth({
    //...其他选项
    advanced: {
        ipAddress: {
          ipAddressHeaders: ["cf-connecting-ip"], // Cloudflare 特定请求头示例
      },
    },
    rateLimit: {
        enabled: true,
        window: 60, // 时间窗口,单位秒
        max: 100, // 窗口内最大请求数
    },
})

IPv6 地址支持

Better Auth 会自动规范 IPv6 地址,以防止攻击者通过使用同一 IPv6 地址的不同表示形式绕过限制(例如 2001:db8::12001:0db8:0000:0000:0000:0000:0000:0001)。这样可以确保对于速率限制来说,所有同一 IPv6 地址的表现形式都被视为相同。

此外,IPv4 映射的 IPv6 地址(例如 ::ffff:192.0.2.1)会自动转换为它们的 IPv4 形式(192.0.2.1),防止攻击者通过在 IPv4 和 IPv6 表示之间切换绕过速率限制。

IPv6 子网速率限制

默认情况下,IPv6 地址按 /64 子网进行速率限制,而不是按单个地址。互联网服务提供商和云服务提供商会分配 IPv6 前缀(通常住宅用户为 /64,参见 RFC 6177),而不是单个地址,因此如果按单个地址计数,某个客户端就可以在不耗尽限制的情况下轮换使用 2^64 个源地址。

如果您的部署需要不同的分配边界,可以通过 ipv6Subnet 选项覆盖前缀长度:

auth.ts
export const auth = betterAuth({
    //...其他选项
    advanced: {
        ipAddress: {
            ipv6Subnet: 56, // 按 /56 子网进行速率限制(住宅 ISP 分配)
        },
    },
    rateLimit: {
        enabled: true,
        window: 60,
        max: 100,
    },
})

常见的 IPv6 前缀长度:

  • 128:单个 IPv6 地址。限制最严格。仅在您控制网络且信任每个地址都对应不同客户端时才安全。
  • 64(默认):/64 子网。典型的家庭或企业分配。
  • 56:/56 子网。参见 RFC 6177 的住宅 ISP 分配。
  • 48:/48 子网。更大的网络分配。
  • 32:/32 子网。ISP 级别分配。

接受 0128 之间的任意整数前缀长度;上述值最为常见。

IPv6 子网配置仅影响 IPv6 地址。IPv4 地址始终按单个地址进行速率限制。

速率限制时间窗口

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
        window: 60, // 时间窗口,单位秒
        max: 100, // 窗口内最大请求数
    },
})

您也可以为特定路径传递自定义规则。

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
        window: 60, // 时间窗口,单位秒
        max: 100, // 窗口内最大请求数
        customRules: {
            "/sign-in/email": {
                window: 10,
                max: 3,
            },
            "/two-factor/*": async (request)=> {
                // 自定义函数用于返回速率限制窗口和最大请求数
                return {
                    window: 10,
                    max: 3,
                }
            }
        },
    },
})

如果想要禁用特定路径的速率限制,可以设置为 false,或者自定义规则函数返回 false

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
        customRules: {
            "/get-session": false,
        },
    },
})

存储

默认情况下,速率限制数据存储在内存中,这可能不适合许多用例,尤其是在无服务器环境中。为此,您可以使用数据库、二级存储或自定义存储来存储速率限制数据。

使用数据库

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
        storage: "database",
        modelName: "rateLimit", // 可选,默认使用 "rateLimit"
    },
})

请确保运行 migrate 命令在数据库中创建速率限制表:

npx auth@latest migrate

migrate 命令仅在使用内置的 Kysely 适配器时有效。如果您使用 Prisma、Drizzle 或其他 ORM,请先执行 npx auth@latest generate,然后通过您使用的 ORM 迁移工具应用 schema。详情请参阅 Better Auth CLI

使用二级存储

如果已经配置了二级存储,您可以使用它来存储速率限制数据。

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
		storage: "secondary-storage"
    },
})

自定义存储

如果上述方式都不适合您的用例,您可以实现一个 customStorage

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

export const auth = betterAuth({
    //...其他选项
    rateLimit: {
        customStorage: {
            get: async (key) => {
                // 获取速率限制数据
            },
            set: async (key, value) => {
                // 设置速率限制数据
            },
        },
    },
})

处理速率限制错误

当请求超过速率限制时,Better Auth 会返回以下响应头:

  • X-Retry-After:用户在多少秒后可以再次发起请求。

在客户端处理速率限制错误时,您可以全局处理或者针对单个请求分别处理。因为 Better Auth 客户端封装了 Better Fetch,您可以传递 fetchOptions 来处理速率限制错误。

全局处理

auth-client.ts
import { createAuthClient } from "better-auth/client";

export const authClient = createAuthClient({
    fetchOptions: {
        onError: async (context) => {
            const { response } = context;
            if (response.status === 429) {
                const retryAfter = response.headers.get("X-Retry-After");
                console.log(`超过速率限制。请在 ${retryAfter} 秒后重试`);
            }
        },
    }
})

按请求处理

auth-client.ts
import { authClient } from "./auth-client";

await authClient.signIn.email({
    fetchOptions: {
        onError: async (context) => {
            const { response } = context;
            if (response.status === 429) {
                const retryAfter = response.headers.get("X-Retry-After");
                console.log(`超过速率限制。请在 ${retryAfter} 秒后重试`);
            }
        },
    }
})

数据库表结构

如果您使用数据库存储速率限制数据,需要以下表结构:

表名:rateLimit

Table
字段
类型
描述
id
string
PK
Database ID
key
string
-
Unique identifier for each rate limit key
count
integer
-
Number of requests made in the current window
lastRequest
bigint
-
Timestamp of the last request (epoch ms)