Fastify Better Auth Bridge 用于把 Better Auth 的会话解析接入 Fastify 5,方式是通过 onRequest 钩子完成桥接。当 Better Auth 已经存在、但 Fastify 缺少对应的框架桥接时,可以使用这个技能。
适用与不适用
适用场景:Better Auth 已存在,需要为 Fastify 5 补上框架桥接。
不应加载该技能的情形:问题出在回调 URL 配置、可信来源(trusted origins)或提供方引导顺序;问题出在 IDCS 组映射或 org_members 供应规则;目标是围绕 Better Auth 另建一套并行会话系统。
必须避免的做法
不要在只需要转发 cookie 头的情况下另建并行会话层——症状(没有会话)与修复(转发 cookie)之间隔着两层,很容易误诊。
不要在桥接的 onRequest 钩子里执行 RBAC 策略——桥接负责解析身份,路由守卫负责执行访问控制;把两者混在一起会让双方都难以测试。
不要在 Fastify 5 中共享可变的数组装饰器默认值——原型上的数组会被所有请求共享;应使用基于 Symbol 的每请求 getter/setter。
不要跳过 Web Request 桥接——auth.api.getSession() 需要原生 Web API Request,而不是 Fastify 请求对象;传入错误类型会静默地返回无会话。
不要假设在 Fastify 5 中 reply.send(undefined) 是安全的——它会抛出异常。
几个不明显的要点
为什么 cookie 转发会静默失败:缺少 cookie 头会让登录和会话解析在完全不同的位置失败——登录成功(在浏览器中设置了 cookie),但下一个请求没有会话。没有报错,会话只是 null。桥接与登录流程看起来互不相关。始终要确认你构建的 Web Request 中包含了 cookie。
为什么 Fastify 5 的装饰器数组会共享状态:fastify.decorateRequest('permissions', []) 中的 [] 是原型默认值,被所有请求共享。第一个请求修改它,第二个请求就会看到上一个请求的权限。应使用以 Symbol 为键的 getter/setter,为每个请求分配新数组。
为什么 IDCS 用户没有活动组织:IDCS 供应的用户是通过组同步创建的,而不是通过 Better Auth 的组织切换流程。他们的会话没有 activeOrganizationId。期望组织上下文的下游代码会静默收到 undefined。当 activeOrganizationId 缺失时,可通过查询 org_members 来修补。
所需的桥接形态
第 1 步:Web Request 桥接。由协议 + 主机名 + URL + 方法 + 完整传入头构建。头中必须包含 cookie——这是 auth.api.getSession() 能解析会话的唯一途径。
第 2 步:一次性装饰请求状态。统一为:request.user、request.session、request.permissions、request.apiKeyContext。对任何持有数组或可变对象的字段,使用基于 Symbol 的 getter/setter。
第 3 步:路径排除。仅对以下路径跳过会话解析:health、metrics、Better Auth 处理路由。匹配前先规范化查询字符串和尾部斜杠——规范化不一致会导致某些路由上出现认证遗漏。
第 4 步:解析会话,出错时以匿名身份继续。如果 auth.api.getSession() 抛出异常:记录日志,并以 request.user = null 继续。绝不要在桥接层拒绝请求——那是路由守卫的职责。
第 5 步:修补组织上下文。如果 request.session.activeOrganizationId 缺失,按用户 ID 查询 org_members 并在下游设置组织上下文。仅在组织上下文确实缺失时这样做——不要把它当作每个请求的默认回退。
诊断脚本
node scripts/check-fastify-auth-bridge.js /path/to/auth-plugin.ts
未提供参数时默认使用 apps/api/src/plugins/auth.ts
参数
$ARGUMENTS:可选,指向要检查的 Fastify 认证插件文件路径。为空 = 使用仓库默认路径(apps/api/src/plugins/auth.ts)。