安全插件(sentinel)

`sentinel()` 插件为你的身份验证系统提供全面的安全和滥用防护。它能够检测并防止多种攻击手段,包括凭证填充、不可能的旅行、免费试用滥用等

安装

import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    sentinel({
      security: {
        // 在此配置安全功能
      },
    }),
  ],
});

配置选项

SentinelOptions

选项类型描述
apiUrlstringBetter Auth Infrastructure API URL。默认值:https://dash.better-auth.com
kvUrlstringBetter Auth 身份识别基础设施 URL。默认值:https://kv.better-auth.com
apiKeystring用于身份验证的 API 密钥。未设置时回退到环境变量 BETTER_AUTH_API_KEY。
apiOptionsobjectDash API HTTP 选项。接受 timeout?: number(毫秒,默认 3000)和 headers?: Record<string, string>。
kvOptionsobjectKV HTTP 选项。接受 timeout?: number(毫秒,默认 1000)、retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } 和 headers?: Record<string, string>。
apiTimeoutnumberapiOptions.timeout 的弃用别名。
kvTimeoutnumberkvOptions.timeout 的弃用别名。
securitySecurityOptions安全功能配置。

SecurityOptions

interface SecurityOptions {
  unknownDeviceNotification?: boolean;
  credentialStuffing?: CredentialStuffingConfig;
  impossibleTravel?: ImpossibleTravelConfig;
  geoBlocking?: GeoBlockingConfig;
  botBlocking?: boolean | { action: SecurityAction };
  suspiciousIpBlocking?: boolean | { action: SecurityAction };
  velocity?: VelocityConfig;
  freeTrialAbuse?: FreeTrialAbuseConfig;
  compromisedPassword?: CompromisedPasswordConfig;
  emailValidation?: EmailValidationConfig;
  emailNormalization?: { enabled?: boolean };
  staleUsers?: StaleUsersConfig;
  challengeDifficulty?: number;
}

type SecurityAction = "log" | "challenge" | "block";

安全功能

凭证填充防护

通过跟踪每个访客的登录失败次数,检测并阻止凭证填充攻击。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    credentialStuffing: {
      enabled: true,
      action: "challenge", // optional default action; thresholds still drive challenge vs block
      thresholds: {
        challenge: 3,  // 失败 3 次后发起 PoW 挑战
        block: 5,      // 失败 5 次后封禁
      },
      windowSeconds: 3600,     // 1 小时时间窗口
      cooldownSeconds: 900,    // 封禁后 15 分钟冷却期
    },
  },
}),

工作原理:

  1. 跟踪每个访客 ID 的登录失败尝试次数
  2. 达到挑战阈值后,发出工作量证明(PoW)挑战
  3. 达到封禁阈值后,完全封禁访客
  4. 登录成功后自动清除失败尝试记录

不可能的旅行检测

检测用户在极短时间内从地理上相距遥远的地方登录。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    impossibleTravel: {
      enabled: true,
      maxSpeedKmh: 1000,    // 最大合理旅行速度
      action: "challenge",  // 可选:"log"、"challenge"或"block"
    },
  },
}),

示例: 如果用户先后在纽约和 30 分钟后在东京登录,这被视为不可能的旅行(即需要超过 1000 公里/小时的速度)。

免费试用滥用防护

通过设备指纹防止用户创建多个账户以滥用免费试用。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    freeTrialAbuse: {
      enabled: true,
      thresholds: {
        challenge: 2,
        block: 3,
      },
      maxAccountsPerVisitor: 3,
      retentionSeconds: 90 * 24 * 60 * 60, // optional; default 90 days
      action: "block",
    },
  },
}),

工作原理:

  1. 跟踪每个访客指纹的账户创建次数
  2. 当超过阈值时,阻止新账户创建
  3. 用于防止免费层滥用

retentionSeconds 控制每位访客的注册记录键保留时长(滑动窗口)。默认值为 90 天;平台会将值限制在 1 天到 365 天之间。

已泄露密码检测

通过查询 HaveIBeenPwned 数据库检查密码,检测泄露凭证。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    compromisedPassword: {
      enabled: true,
      action: "block",      // 可选:"log"、"challenge"或"block"
      minBreachCount: 1,    // 触发的最小泄露次数
    },
  },
}),

隐私说明: 采用 k-匿名技术 — 仅将密码哈希的前 5 个字符发送到 API,完整密码绝不发送。

过期账户监控

检测沉睡账户突然活跃,可能表示账户被接管。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    staleUsers: {
      enabled: true,
      staleDays: 90,           // 账户超过 90 天视为沉睡
      action: "log",           // 可选:"log"、"challenge"或"block"
      notifyUser: true,        // 向用户发送邮件通知
      notifyAdmin: true,       // 向管理员发送邮件通知
      adminEmail: "[email protected]",
    },
  },
}),

通知包括:

  • 登录时间和位置
  • 距离上次活跃的天数
  • 设备信息

地理封禁

封禁或挑战特定国家的用户。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    geoBlocking: {
      allowList: ["US", "CA", "GB"],  // 仅允许这些国家
      // 或者
      denyList: ["XX", "YY"],          // 封禁这些国家
      action: "block",                  // 可选:"challenge"或"block"
    },
  },
}),

使用 ISO 3166-1 alpha-2 国家代码。

机器人封禁

检测并阻止自动化机器流量。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    botBlocking: true,
    // 或使用自定义操作
    botBlocking: {
      action: "challenge",  // 可选:"log"、"challenge"或"block"
    },
  },
}),

可疑 IP 检测

封禁来自已知恶意 IP 地址的请求。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    suspiciousIpBlocking: true,
    // 或使用自定义操作
    suspiciousIpBlocking: {
      action: "block",
    },
  },
}),

访问速率限制 / 频率控制

限制各种操作的请求速率。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    velocity: {
      enabled: true,
      thresholds: {
        challenge: 10,
        block: 20,
      },
      maxSignupsPerVisitor: 5,
      maxPasswordResetsPerIp: 10,
      maxSignInsPerIp: 50,
      windowSeconds: 3600,
      action: "challenge",
    },
  },
}),

邮箱验证

封禁一次性邮箱地址并验证邮箱域名。

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    emailValidation: {
      enabled: true,
      strictness: "medium",  // 可选:"low"、"medium"、"high"
      action: "block",
      domainAllowlist: ["company.com", "partner.org"], // always allow these domains
    },
  },
}),

严格级别:

  • low - 仅阻止已知一次性域名
  • medium - 还检查有效的 MX 记录
  • high - 额外的启发式检查

domainAllowlist 会跳过所列域名的一次性邮箱和启发式检查(必须精确匹配域名)。

邮箱规范化

Sentinel 会在注册和登录之前规范化邮箱地址,以防止别名和提供商的怪癖导致重复账户或登录不匹配。规范化包括将邮箱转为小写、在常见提供商上移除 plus-address 标签(例如 [email protected] → [email protected])、移除 Gmail 风格地址中的点,以及将 googlemail.com 映射到 gmail.com。

当你希望将此功能与一次性域名验证(emailValidation)分开控制时,请使用 security.emailNormalization:

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    emailValidation: {
      enabled: false, // 跳过一次性 / MX 检查
    },
    emailNormalization: {
      enabled: true, // 仍进行规范化以去重和保持一致的登录
    },
  },
}),

工作量证明挑战

当安全检查导致“challenge”动作时,Sentinel 会发起工作量证明(PoW)挑战,客户端必须完成挑战。

PoW 工作原理

  1. 服务器发出加密挑战
  2. 客户端必须找到满足难度要求的解答
  3. 解答计算开销大,但验证快速
  4. 有效阻止自动化攻击,允许合法用户通过

挑战难度

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    challengeDifficulty: 18,  // 默认难度等级
  },
}),

难度越高 = 计算量越大 = 攻击者越慢。

客户端集成

sentinelClient()

客户端插件负责设备指纹识别及自动完成 PoW 挑战。

import { createAuthClient } from "better-auth/client";
import { sentinelClient } from "@better-auth/infra/client";

export const authClient = createAuthClient({
  plugins: [
    sentinelClient({
      autoSolveChallenge: true,
    }),
  ],
});

配置项

选项类型默认值描述
identifyUrlstringBETTER_AUTH_KV_URL 或 https://kv.better-auth.comidentify 端点的基础 URL。
identifyOptionsobject{ timeout: 1000, retry: { attempts: 2, baseDelay: 400, maxDelay: 600 } }Identify HTTP 超时与重试策略。
autoSolveChallengebooleantrue自动解决 PoW 挑战。
kvTimeoutnumber1000identifyOptions.timeout 的弃用别名。
onChallengeReceived(reason: string) => void—收到 PoW 挑战时调用。
onChallengeSolved(solveTimeMs: number) => void—成功解决挑战后调用。
onChallengeFailed(error: Error) => void—解决挑战失败时调用。

浏览器指纹

客户端自动在请求中通过 X-Visitor-Id 头包含访客 ID。此指纹用于:

  • 凭证填充检测
  • 免费试用滥用防护
  • 设备跟踪

PoW 解答请求头

启用自动解决后,解答通过 X-PoW-Solution 头发送。

Expo 和 React Native

对于 Expo 和 React Native 应用,使用 @better-auth/infra/native 中的 sentinelNativeClient,而不是使用 @better-auth/infra/client。

对于 React Native 应用,安装以下对等依赖:

npm install @react-native-async-storage/async-storage react-native-get-random-values

@react-native-async-storage/async-storage 是可选的。如果未安装,客户端使用仅会话的内存访客 ID。对于生产环境,请安装它或传递自定义 storage(例如安全存储)。

示例

import { createAuthClient } from "better-auth/client";
import { dashClient, sentinelNativeClient } from "@better-auth/infra/native";

export const authClient = createAuthClient({
  baseURL: "https://your-api.example.com",
  plugins: [
    dashClient(),
    sentinelNativeClient({
      autoSolveChallenge: true,
    }),
  ],
});

对于 Expo 应用,安装以下软件包以获得更丰富的原生身份信息。

Expo: 在添加基础设施插件之前,请遵循 Expo 集成 指南。

npm install expo-constants expo-device expo-crypto

示例

import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import { dashClient, sentinelNativeClient } from "@better-auth/infra/native";
import * as SecureStore from "expo-secure-store";

export const authClient = createAuthClient({
  baseURL: "https://your-api.example.com",
  plugins: [
    expoClient({
      scheme: "myapp",
      storagePrefix: "myapp",
      storage: SecureStore,
    }),
    dashClient(),
    sentinelNativeClient({
      autoSolveChallenge: true,
    }),
  ],
});

sentinelNativeClient 选项

选项类型默认值描述
identifyUrlstringBETTER_AUTH_KV_URL,然后是 https://kv.better-auth.comIdentify 端点基础 URL
identifyOptionsobject{ timeout: 1000, retry: { attempts: 2, baseDelay: 400, maxDelay: 600 } }Identify HTTP 超时和重试策略
kvTimeoutnumber1000identifyOptions.timeout 的弃用别名
autoSolveChallengebooleantrue遇到带有 X-PoW-Challenge 的 423 时,解决挑战并使用 X-PoW-Solution 重试一次
onChallengeReceived(reason: string) => void—收到 PoW 挑战时调用
onChallengeSolved(solveTimeMs: number) => void—成功解决挑战后调用
onChallengeFailed(error: Error) => void—解决失败时调用
storage{ getItem, setItem }安装后使用 Async Storage用于生成稳定的每次安装一个访客 ID 的持久异步存储

安全事件

Sentinel 跟踪以下安全事件类型:

事件类型描述
security_blocked请求被阻止
security_allowed挑战后请求被允许
security_credential_stuffing检测到凭证填充
security_impossible_travel检测到不可能的旅行
security_geo_blocked触发地理封禁
security_bot_blocked检测并阻止机器人
security_suspicious_ip检测到可疑 IP
security_velocity_exceeded超过速率限制
security_free_trial_abuse检测到免费试用滥用
security_compromised_password检测到泄露密码
security_stale_account沉睡账户重新激活

这些事件可在安全控制台查看,并包含在审计日志中。

完整示例

import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    sentinel({
      apiKey: process.env.BETTER_AUTH_API_KEY,
      security: {
        // 核心防护
        credentialStuffing: {
          enabled: true,
          thresholds: { challenge: 3, block: 5 },
        },
        compromisedPassword: {
          enabled: true,
          action: "block",
        },
        emailValidation: {
          enabled: true,
          strictness: "medium",
          action: "block",
        },
        
        // 基于位置的策略
        impossibleTravel: {
          enabled: true,
          action: "challenge",
        },
        geoBlocking: {
          denyList: ["XX"],
          action: "block",
        },
        
        // 滥用防护
        freeTrialAbuse: {
          enabled: true,
          maxAccountsPerVisitor: 3,
          action: "block",
        },
        velocity: {
          enabled: true,
          maxSignupsPerVisitor: 5,
          action: "challenge",
        },
        
        // 机器人防护
        botBlocking: { action: "challenge" },
        suspiciousIpBlocking: { action: "block" },
        
        // 账户监控
        staleUsers: {
          enabled: true,
          staleDays: 90,
          notifyUser: true,
          notifyAdmin: true,
          adminEmail: "[email protected]",
        },
      },
    }),
  ],
});

最佳实践

  1. 先从日志开始 - 初期将动作设置为“log”,以了解流量模式,再决定是否封禁。

  2. 调整阈值 - 每个应用都不同,监控误报率并相应调整阈值。

  3. 先使用挑战,后使用封禁 - 挑战能阻挡自动化攻击,同时允许合法用户访问。

  4. 启用客户端自动解决 - 在浏览器中使用 sentinelClient({ autoSolveChallenge: true }),或在 Expo / React Native 中使用 sentinelNativeClient({ autoSolveChallenge: true })。

  5. 监控安全事件 - 定期查看安全控制台,识别攻击趋势。

  6. 设置管理员通知 - 对关键事件如沉睡账户激活启用 notifyAdmin。

疑难解答

缺少 API 密钥警告

[Sentinel] 缺少 BETTER_AUTH_API_KEY。安全检查可能回退到允许模式。

请确保正确设置了 API 密钥环境变量。

挑战无法执行

如果 PoW 挑战无法解决:

  1. 验证正确的客户端插件:Web 使用 sentinelClient()(来自 @better-auth/infra/client),Expo / React Native 使用 sentinelNativeClient()(来自 @better-auth/infra/native)
  2. 检查 autoSolveChallenge 是否为 true
  3. 确保客户端可以访问服务器

误报率过高

如果合法用户被阻止:

  1. 增加阈值
  2. 将操作从“block”改为“challenge”
  3. 查看安全事件以识别模式