Better Auth 集成:OAuth 提供方与通行密钥
认证服务通过一个工厂函数来构建它的 Better Auth 实例。这个工厂从环境读取配置,接入基于 Prisma 的存储,挂上通行密钥(passkey)插件,并只注册那些确实配置好的社交登录提供方。在这个库实例之外,还有一层手写的 OAuth 服务,承载着应用真正在意的账号关联与强因子(strong factor)逻辑。这个节点之所以存在,正是因为这两者:库提供原语,服务决定一次登录意味着什么。
工厂
createAuth 接收一个 Prisma client,返回 Better Auth 实例以及规范化后的 base path。在构造任何东西之前它先做快速失败:环境里必须存在签名密钥,否则抛错。base URL 和 base path 都被手动规范化——强制给 path 补上前导斜杠、给两者去掉尾部斜杠——这样无论环境怎么写,重定向 URI 都能可预测地拼出来。
实例的配置包括:
- 存储——基于 PostgreSQL 的 Prisma adapter,会话存进数据库,而不是只留在 token 里。
- 禁用邮箱+密码——这套部署不通过 Better Auth 做密码登录;强因子是 OAuth 和通行密钥。
- 通行密钥插件——用一个 relying-party id、一个 relying-party name,以及一个或多个允许的 origin 来配置。给出多个 origin 时传入列表,只有一个时传入单值。
- 社交登录提供方——按条件加入(见下)。
- 限流——默认开启,可通过配置关掉。
按条件注册社交登录
只有当某个提供方的 client id 和 client secret 都在环境里存在时,它才会被注册。Google 和 LinkedIn 各自都受这道闸把守。对每一个配置好的提供方,工厂用规范化后的 base URL、base path 再加上每个提供方各自的回调段,推导出重定向 URI——回调地址从不手写。如果最终没有任何提供方被配置,工厂只是记一条 warning,而不是失败——服务仍可只靠通行密钥运行。
这条"只配置存在的东西"的规则意味着:一套部署要打开某个提供方,只需提供它的凭据;要关掉,只需省略它。这里没有单独的 feature flag。
薄薄的插件包装
Prisma adapter 和通行密钥插件各自都通过一个只有一行的包装模块引入,把 Better Auth 的构建物以本地名字重新导出。这些包装存在的意义,是给代码库其余部分一个稳定的 import 路径和一个带类型的接口,而不是增加行为——它们是接缝(seam),不是逻辑。
应用层的 OAuth 服务
在库之外,一个 OAuthService 承载着产品自己定义的流程。它在构造时把同样的提供方(Google、LinkedIn)从配置读进一个 map,所以它对那些从未配置过的提供方同样保持沉默。它的职责:
- 换 token——拿授权码去提供方的 token 端点换取 token,失败时把提供方自己的错误描述透出来。
- 取用户信息——用 access token 去提供方的 userinfo 端点拉取资料。
- 登录——按 email(转小写;email 必填)解析账号。如果用户已存在而该提供方尚未关联,就关联上;如果用户不存在,就创建一个——OAuth 的 email 被当作已验证,因为提供方已经验证过——然后关联提供方账号并开启会话。
- 关联——当调用方已经持有一个会话时,同一个登录入口会转向关联路径:它要求会话存在、拒绝重复关联同一个已关联的提供方、记录新账号,并让任何缓存的资料失效。
另有一份并行的提供方清单,列出受支持的提供方(Google、LinkedIn)及其显示名和 connect 路径,让周边的应用能渲染"关联"入口,而不必重复一份提供方知识。
供给强因子生命周期
一次成功的 OAuth 登录会给用户记录一条类型为 oauth 的强因子断言,返回的用户对象被标记为强因子已完成,并带上方法和完成时间。同一个对象还会报告该用户是否注册了任何通行密钥。这就是这些认证路径与账号安全态势之间的接点:OAuth 与通行密钥是用户达成强因子完成的两条路,每一次登录或关联都会更新系统其余部分据以判断"这个账号被允许做什么"的那些标志位。
为什么是这个形状
Better Auth 负责那些手写既繁琐又危险的机制——WebAuthn 仪式、OAuth 往返、会话存储。应用层的 OAuth 服务负责那些专属于本产品的决策——什么算强因子、账号何时是新建何时是关联、注册时可选的"按邮箱域名判定管理员"规则。把这两者分开,产品规则才保持可读,库也能在其下被独立升级。