Skip to main content
Documentation navigation

KAI AUTH / DEVELOPERS

Better Auth 1.7.1Updated Content: Simplified Chinese
View Markdown
On this page

接入诊断与故障协作

诊断分为项目视图、平台视图和可选的应用上报。系统记录观察到的事实,不把 HTTP 状态码、没有后续请求或应用自报直接当作责任归属。诊断采集或告警不可用不会阻止正常登录。

项目与平台能看到什么

项目控制台的“接入诊断”按 Organization → Project → Application 限定访问。具备项目查看权限的成员及组织角色可查询本项目最近 30 天的流程。筛选、统计、详情与 CSV/JSONL 导出使用相同的服务端授权和字段投影;知道 requestId 或 flowId 不会获得查看权限。平台管理员的诊断视图保留最近 90 天的安全运行信息。

项目侧可见应用、时间、阶段、请求编号、粗粒度结果、可执行的错误提示以及与本流程匹配的平台故障处理状态。用户成功同意授权后才会出现项目内用户代号;同一用户跨项目得到不同代号。代号仍可用于同项目内关联活动,不属于不可追踪的完全匿名数据,也没有邮箱反查入口。项目侧不显示姓名、邮箱、手机号、平台用户 UUID、完整 IP、User-Agent、账号是否存在、使用的登录方式、安全因子或其他应用活动。登录与额外验证的内部细节在项目视图统一为身份认证阶段。

平台管理员可见受控内部原因、依赖、发布版本和具体诊断阶段,但密码、验证码、授权码、access/refresh/ID token、Cookie、PKCE verifier、完整 OAuth 查询及上游用户资料均不进入诊断记录。平台故障的内部备注不会出现在项目视图、公共错误或 Lark 卡片里。读取、导出和故障处理修改都会留下管理审计记录。

分类 表示什么 不代表什么
开发者请求/配置 Auth 校验到 scope、回调地址、客户端或协议请求不满足契约 公开 client_id 被使用不证明请求一定来自开发者服务器
平台 Auth 内部数据库、执行或令牌签发等已观察到的技术异常 不推断第三方应用内部情况
依赖 Auth 与身份提供方或其他依赖的交互失败 连接超时不直接证明依赖厂商自身故障
用户/业务规则 取消授权、身份验证未完成或已知账号规则拒绝 不属于服务故障,也不把密码输错逐条发群
未确定 目前没有足够证据说明原因 不默认归责给开发者

“授权码已签发”只表示 Auth 已产生回调;不能证明用户浏览器成功收到回调。“令牌已签发”只表示 Auth 完成令牌请求;不能证明应用已创建本地会话。没有后续请求显示为“未观察到”,不是“应用服务器出错”。应用主动提交的记录始终标记为“应用自报”,不计入 Auth 技术失败告警。

流程的 completion 与单个步骤的 outcome 分开:授权回调发出但没有观察到令牌兑换显示“尚未观察到令牌兑换”;令牌兑换成功但没有应用会话结果显示“应用会话结果未知”。“结果待确认”统计包括这些未完成观测的流程。普通个人中心登录和刷新在 Auth 完成其负责的处理后可以完成,不要求不存在的应用会话上报。应用报告会话创建成功始终标明“应用自行上报”,不能作为 Auth 独立证明的端到端成功。

请求编号与流程编号

requestId 是单个服务端请求的编号,flowId 关联一次已验证的 OAuth 授权流程。浏览器的多标签和不同授权请求不会仅因用户相同而合并。项目归属来自服务端验证的应用登记与绑定,不信任调用方自报的项目 ID 或关联请求头。授权码兑换与刷新由服务端 grant 关系关联,不记录凭据原文。每次 refresh 使用新的独立 flow;平台内部可通过 grantReference 关联同一授权记录,项目视图不提供该字段。它是已有授权记录标识的摘要,不是 token,也不是可兑换的授权码。

登录页遇到 Lark 等外部身份提供方错误时,使用对应的稳定错误码和本地化操作说明;HTTP 302 跳转成功与业务认证失败分别记录。账号关联冲突不再被等同于“获取资料失败”。

可选:补齐应用回调后的观测

普通 OAuth 接入不需要改变。需要查看应用内部阶段时,在原有 authorize 请求增加 kai_diagnostics=1。Auth 只向已验证登记的回调地址附加 kai_flow_id 和 kai_diagnostic_token,原有 state、授权码和错误语义保持不变。

诊断 token 是仅能向单次流程追加事件的短期凭据,15 分钟有效、最多 12 个唯一事件,不能获取账号、会话或令牌。仅保存在应用回调处理期间的内存中,不放入日志、分析系统、localStorage 或持久 Cookie。网页应用应立即从地址栏移除两个诊断参数;服务端应避免访问日志收集回调查询。不能把 kai_flow_id、诊断 token 或诊断成功当成 OAuth 登录的真实性证明;仍必须正常验证 state、PKCE、nonce、issuer、audience 和令牌签名。

POST /api/auth/diagnostics/report 使用 Authorization: Bearer <kai_diagnostic_token>。正文只接受 eventId、code、stage、outcome 及可选 durationMs、requestId,不支持任意日志文本、邮箱或 URL。同一 eventId 和同一正文重试幂等;相同 ID 配不同内容会拒绝。客户端不能自行指定项目或责任分类。

code stage outcome
CALLBACK_RECEIVED callback_received success
CALLBACK_REJECTED / STATE_MISMATCH callback_received failure
TOKEN_RECEIVED / TOKEN_EXCHANGE_FAILED token_exchange success / failure
TOKEN_VALIDATED / TOKEN_VALIDATION_FAILED id_token_validation success / failure
SESSION_CREATED / SESSION_CREATION_FAILED session_establishment success / failure
CLIENT_CANCELLED session_establishment cancelled
CLIENT_UNKNOWN_FAILURE session_establishment failure

下例使用仓库 examples/auth-diagnostics/report.mjs 的完整辅助函数。finishExistingOAuthLogin 是应用现有的登录完成入口,必须验证 state、PKCE、nonce、issuer、audience、签名并成功创建应用会话。诊断参数在交给该入口之前从地址栏移除,标准 OAuth 参数保持不变;服务端回调同样不要把查询串写入访问日志。

import { takeDiagnosticCallback, createDiagnosticReporter } from "./report.mjs"

const callback = takeDiagnosticCallback(window.location.href)
window.history.replaceState(null, "", callback.cleanUrl)
let reporter = null
try {
  reporter = createDiagnosticReporter({
    authOrigin: "https://auth.kai.com", // 来自应用配置,不能读取回调参数
    token: callback.token,
  })
} catch {
  // 可选诊断配置错误不阻断正常 OAuth;不记录回调或凭据。
} finally {
  callback.token = null
}

const session = await finishExistingOAuthLogin(callback.cleanUrl).catch(
  error => {
    reporter?.dispose()
    throw error // 保留原本的 OAuth/业务会话失败。
  },
)
if (reporter) {
  const reporting = reporter
  reporter = null
  void reporting.report("SESSION_CREATED").finally(() => reporting.dispose())
}

辅助函数在内存中保留诊断凭据,15 分钟后自动清除;登录失败立即释放,登录成功的可选上报在后台完成后释放。上报不延迟 session 的使用,不影响已有 OAuth 的校验或错误处理。

使用应用配置的 Auth 地址,不能从回调参数决定投递地址。该上报端点允许不携带 Cookie 的跨域请求;Bearer 凭据只可追加本次流程的有限事件,不提供诊断读取权限。429、网络失败或服务停机时不循环重试,不延长登录耗时。返回成功仅表示 Auth 接受了一条“应用自报”,不证明应用正确完成认证。

服务端和原生应用使用相同 HTTP 契约。requestId 和 eventId 必须是 UUID;durationMs 为 0–900000 的整数。不要上传 stack、姓名、邮箱、URL 或自由文本。仓库中的 examples/auth-diagnostics/report.mjs 提供无依赖完整辅助函数,支持最多 12 次上报、2 秒超时、凭据过期和主动清理;对应测试使用 node --test examples/auth-diagnostics/report.test.mjs。

诊断错误可以按下表处理,具体业务 OAuth 错误仍按错误码文档处理:

错误码 处理
DIAGNOSTIC_REPORT_TOKEN_INVALID 诊断凭据无效或到期,停止本次上报,不重新登录用户
DIAGNOSTIC_REPORT_EVIDENCE_INVALID code、stage、outcome 不匹配,修正应用映射
DIAGNOSTIC_REPORT_IDEMPOTENCY_CONFLICT 同一 eventId 被用于不同内容,修正幂等实现
DIAGNOSTIC_REPORT_LIMIT_REACHED 已达到单流程 12 个唯一事件的限额,停止上报
DIAGNOSTIC_REPORT_RATE_LIMITED 按 Retry-After 等待;不要自动重走 OAuth
DIAGNOSTIC_REPORT_TOO_LARGE 正文超过 4096 字节,移除契约外数据

Web、iOS 与 CLI 接入示例

三个可运行示例位于仓库 examples/auth-diagnostics/。它们只补充应用上报,现有 OAuth/OIDC 库仍负责所有认证验证、业务会话创建与凭据保存;不要把示例中的诊断 token 当成 OAuth token。

  • Web:使用 report.mjs 的 takeDiagnosticCallback 和 createDiagnosticReporter。回调加载后立即 history.replaceState 移除诊断参数,保留标准 code/state 供 OIDC 库处理。报告 token 仅保存在内存,15 分钟自动清除,完成后调用 dispose()。
  • iOS:ios.swift 提供可编译的 completeIOSOAuthCallback、Actor reporter 和 URLSession 实现。把 ASWebAuthenticationSession 返回的 URL 交给它;回调诊断参数会在交给现有 OIDC SDK 前移除。通过 authenticate 闭包调用应用现有的授权码兑换、令牌验证和会话保存,只有闭包成功返回才报告 SESSION_CREATED。临时 URLSession 不共享 Cookie、URL 缓存或 HTTP 凭据,拒绝重定向,每次请求最多 2 秒,内存 token 15 分钟自动清除。报告在独立 Task 中执行,不延迟登录结果。
  • Node.js CLI:cli.mjs 的 completeCliOAuthCallback 用于现有 loopback HTTP 回调处理器,不要把完整回调 URL 或诊断 token 放在命令行参数、终端历史或配置文件。它在交给现有 OIDC 实现前移除诊断参数;上报通过 fetch、不使用 Cookie、禁止重定向,完成或失败后释放内存 token。

CLI 调用形态如下,finishExistingOAuthLogin 是当前 CLI 已有的、完整校验并保存应用会话的实现:

import { completeCliOAuthCallback } from "./cli.mjs"

const session = await completeCliOAuthCallback(callbackUrl, {
  authOrigin: configuredAuthOrigin,
  authenticate: finishExistingOAuthLogin,
})

iOS 对应调用形态如下,existingOIDCFlow.finish 是当前 App 已有的 OIDC 完成入口;SDK 和业务会话失败仍按原行为向调用方返回,诊断失败不会覆盖它:

let session = try await completeIOSOAuthCallback(
    callbackURL,
    authOrigin: configuredAuthOrigin
) { cleanURL in
    try await existingOIDCFlow.finish(callbackURL: cleanURL)
}

这些入口不会根据任意异常消息猜测具体失败原因,未知应用异常只报告 CLIENT_UNKNOWN_FAILURE。若要区分 token 兑换和 ID token 校验失败,请在相应的真实处理步骤调用 typed DiagnosticEvidence 或 JS 的闭合集合 code,不上传异常文本。

示例的本地验证命令(不访问真实 Auth 或 Lark)为:

node --test examples/auth-diagnostics/report.test.mjs examples/auth-diagnostics/cli.test.mjs
swiftc -swift-version 6 -parse-as-library examples/auth-diagnostics/ios.swift examples/auth-diagnostics/ios-test.swift -o /tmp/kai-diagnostics-ios-example-test
/tmp/kai-diagnostics-ios-example-test

Swift 示例使用 ContinuousClock 和 async URLSession,要求 iOS 16 / macOS 13 或更新系统。它不修改 App 的原有登录协议,也不注册新的回调方案。

聚合告警与恢复

后台 worker 读取已落库的结构化记录。可信技术告警只使用 Auth 自己观察到的事件;项目级统计需要受信应用归属,未归属的平台异常仅用于全局运行诊断。

规则 触发条件
相同平台/依赖异常 5 分钟内至少 5 个不同 flow
技术失败率异常 5 分钟内至少 50 个已完成观测 flow、至少 5 个技术失败 flow,失败率达到 10%
未知异常 新指纹首次出现即记录,30 分钟内合并
业务体验异常 10 分钟内特定账号规则拒绝达到 20 个 flow 且占该阶段观测 flow 至少 30%;低优先级,不包括普通密码/验证码输入错误
恢复 连续两个互不重叠的 5 分钟窗口没有同类失败,且两个窗口都有真实成功流量

技术失败率仅计算最新平台事件体现的终态:已验证应用的 token/refresh 成功,未关联 OAuth 应用的直接登录已完成会话建立,或上述流程的明确失败。OAuth 流程的 authorize/authentication 中间成功、发送验证码和等待验证器批准都不扩大分母;用户取消和预期业务拒绝不计技术失败分子。恢复窗口采用同样的完成口径,没有流量不会自动宣布恢复;错过的恢复窗口不会补算为成功。

上述统计仅采用 Auth 自身的事件,包含已验证应用的流程及未关联应用的直接登录。服务端已确认的 Lark 身份冲突等也适用体验异常阈值;客户端上报和未经验证的应用归属不进入告警聚合。普通密码、验证码错误和明确限流保留在诊断中,不触发未知异常首报。

指纹由错误码、阶段、依赖、环境组成,项目作为受影响范围而不是每项目重复通知。同一事件的首次通知、每 30 分钟的持续摘要和恢复通知分别去重。故障支持“调查中、已确认、观察恢复、已解决”、指派平台管理员和内部备注;手动标记已解决只是人工处理状态,不伪造“自动验证已恢复”的通知。新失败仍可创建新故障。

管理员告警链接使用 ?incident=<故障编号> 定位具体事件,保留期内的旧故障不受列表首页数量限制。故障列表按最近发生时间分页,支持按处理状态筛选和继续加载;查询与定位仍要求当前平台管理员权限,平台故障查询同样限制在最近 90 天。

Lark 卡片只包含固定模板、安全错误码、汇总流程数、时间、发布版本、示例 flowId 及需登录的平台控制台链接。不包含用户身份、登录因子、项目内部详情、Webhook 或自由文本内部备注。测试投递仅发送固定测试卡,页面先显示“已排队”;可查询投递状态,不能把入队当作已送达。

配置、观察期与发布

采集、项目展示、应用上报分别由 DIAGNOSTICS_CAPTURE_ENABLED、DIAGNOSTICS_PROJECT_ENABLED、DIAGNOSTICS_REPORT_ENABLED 控制,默认关闭。项目内用户代号使用独立的 DIAGNOSTICS_PSEUDONYM_SECRET 与 DIAGNOSTICS_PSEUDONYM_KEY_VERSION;不要复用 OAuth、会话、验证码或其他日志系统的密钥。账号删除时清除关联诊断的用户引用、用户代号与 grantReference,包括尚未形成授权同意或用户代号的流程。延迟采集不会重新建立已删除账号的关联。

采集队列有固定容量与写入时限。队列溢出、写入超时或进程重启可能造成观测缺口;平台可查看采集状态、待写入数量与丢失计数,数据不足时不把它当作完整可用率。报告凭据与安全开关不会改变 OAuth 登录权限。

DIAGNOSTICS_ALERTS_ENABLED 控制后台聚合,DIAGNOSTICS_ALERTS_SEND_ENABLED 控制自动对外发送;二者默认关闭。Webhook 通过服务端 DIAGNOSTICS_LARK_WEBHOOK 注入,可选签名密钥通过 DIAGNOSTICS_LARK_SIGNING_SECRET 注入,控制台不接收、不显示密钥。

只允许国际版 Lark 官方 https://open.larksuite.com/open-apis/bot/v2/hook/... 地址,不接受任意 URL、凭据、查询参数或重定向。签名遵循 Lark 自定义机器人官方说明。投递设置不复用面向用户的邮箱/短信渠道。

首次启用聚合后有至少 24 小时观察期,起始时间持久化,部署和重启不会重新计时。观察期只记录模拟通知;期满仍需要显式打开发送开关。观察期积累的旧通知不会批量补发。平台管理员可以单独请求固定安全测试卡验证通道,不改变正式发送开关。

队列在事务中认领,有 30 秒租约;每次网络请求限制 5 秒、禁止跟随重定向并限制响应体大小。失败采用 30 秒、2 分钟、5 分钟、15 分钟退避,最多 5 次,之后进入失败队列;全局每分钟最多 5 次、每小时最多 30 次投递尝试。超时后的远端结果可能不确定,Lark Webhook 没有业务幂等承诺,因此极少量重复卡片仍可能出现,卡片中的故障编号/详情链接可识别同一事件。

告警自身的数据库或投递失败显示在 worker/队列状态中,不再递归产生群告警。服务或数据库整体不可用时,进程内 worker 无法独立证明状态,需要部署方已有的外部健康检查补充;“无日志”不能当作“服务正常”。

推荐启用顺序:应用兼容数据库迁移 → 启用采集 → 验证项目隔离与错误提示 → 启用聚合观察 24 小时 → 测试安全卡 → 核对阈值和噪声 → 显式开启自动发送。关闭发送开关立即停止自动投递,保留诊断和处理状态供排查;回滚应用不删除新增诊断表。