@ bsuite/ auth (英语)
OAuth 2.1 PKCE客户端在BSuite应用程序(CRM7, Conduit, R80.4, Braden, Turput)之间共享. BSU是 OAuth 服务器;这个软件包是用于消耗它的其他应用程序的客户端.
同步登录和召回恢复
选入 届会所有权协调员 序列化 消费者SDK在同源标签上写作和登录。 消费者适配器接线, 交易快照约束和部署的核查仍然是必要的.
登录启动器应当使用 attemptSilentAuthDetailed()。继续使用 authenticated; 暂停 redirecting 或者说 superseded; 提供交互式恢复 failed。旧布尔帮助器仍然兼容,但无法区分预定导航和失败。 指派 window.location 不停止后续的 JavaScript .
路由调用 hasPendingBusinessSuiteTransaction(state),这需要这个来源的完整,新的交易状态. 不从状态形状或存在不相关的后遗症键发送 。 遗产交易还需要其核查人、非记录和时间戳。 交易不完全需要新的签注 .
BusinessSuiteOAuthExchangeUncertainError.recovery 实值 fresh-sign-in:服务器可能在网络或响应-body故障前消耗了单用代码. 永远不要自动重放它。 回调错误处理器必须保存不相关的交易和较新的会话. 在开始交换之前抓取消费者的会话/日志生成器,并在核实后以及在进行信使或导航之前再次检查。 已经在飞行中的交换可以在签出后返回;其结果不得恢复上一个会话. 成功交换后,通过消费者的桥梁 supabase.auth.setSession() 在保护数据读取之前。 主机-本地会话存储仍然与跨应用 OAuth SSO 分离.
套件是故意小的——它默认生产出BSuite Supabase主机(https://tuybltdrdefjblnplpqo.supabase.co),支持针对持续开发的Supabase分支的 OAuth-server 明确覆盖,解决从 URI重定向 VITE_APP_URL (或 NEXT_PUBLIC_APP_URL)在有可用的情况下,并回到 ${window.location.origin}/auth/callback它只取决于 jose jWKS验证.
OAuth 服务器环境
生产消费者不需要额外的配置;OAuth服务器默认为生产出BSuite Supabase项目. 使用持续Supabase分支/项目的长期开发部署,必须设定该分支URL的其中一个变量:
VITE_BSU_OAUTH_SUPABASE_URL用于构建 Vite 浏览器NEXT_PUBLIC_BSU_OAUTH_SUPABASE_URLnext.js 浏览器构建BSU_OAUTH_SUPABASE_URL用于服务器/构建时环境
接受的别名是 VITE_BUSINESS_SUITE_SUPABASE_URL, (中文(简体) ), NEXT_PUBLIC_BUSINESS_SUITE_SUPABASE_URL,以及 BUSINESS_SUITE_SUPABASE_URL。数值必须是表格中的 HTTPS Supabase 项目 URL https://<project-ref>.supabase.co;在将用户重定向到破损的 OAuth 流之前,无效的覆盖 .
安装
pnpm add @bsuite/auth
使用量
每个消费者应用都创建了薄薄的包装器,将共享的客户端再出口到其OAuth客户端ID上. 从CRM7中取出的例子(犬科参考文献——相同模式 R80.4, (中文(简体) ), braden, (中文(简体) ), conduit, (中文(简体) ), throughput:
// src/lib/business-suite-oauth.ts
import { createOAuthClient } from '@bsuite/auth';
export type { BusinessSuiteTokens, VerifiedUser } from '@bsuite/auth';
export const {
signInWithBusinessSuite,
exchangeCodeForTokens,
refreshBusinessSuiteToken,
verifyAccessToken,
getUserInfo,
clearBSTokens,
startBSTokenRefresh,
attemptSilentAuthDetailed,
} = createOAuthClient('30f76744-3e0b-40bf-abb8-8c587389802e'); // your app's OAuth client ID
然后把它接入授权的生命周期:
// On a login button click — schedules navigation to the OAuth server
void signInWithBusinessSuite();
// In your /auth/callback route, with `code` and `state` parsed from the URL
const { tokens, user } = await exchangeCodeForTokens(code, state);
localStorage.setItem('bs_access_token', tokens.access_token);
localStorage.setItem('bs_refresh_token', tokens.refresh_token);
localStorage.setItem('bs_user', JSON.stringify(user));
if (tokens.id_token) localStorage.setItem('bs_id_token', tokens.id_token);
// On app mount (e.g. AuthContext / AuthProvider) — try OIDC silent re-auth before showing the login UI
const result = await attemptSilentAuthDetailed();
// Stop on redirecting/superseded; navigate only on authenticated.
// Start the auto-refresh loop and capture the cleanup function
const stopRefresh: () => void = startBSTokenRefresh();
// later, e.g. on AuthContext unmount or sign-out:
stopRefresh();
// Sign-out — clear all 4 BS OAuth localStorage keys
clearBSTokens();
API 密码
import type { OAuthClient, BusinessSuiteTokens, VerifiedUser } from '@bsuite/auth';
declare function createOAuthClient(clientId: string): OAuthClient;
interface OAuthClient {
signInWithBusinessSuite(): Promise<void>;
exchangeCodeForTokens(
code: string,
state: string,
): Promise<{ tokens: BusinessSuiteTokens; user: VerifiedUser }>;
refreshBusinessSuiteToken(
refreshToken: string,
): Promise<{ tokens: BusinessSuiteTokens; user: VerifiedUser }>;
verifyAccessToken(token: string): Promise<VerifiedUser>;
getUserInfo(accessToken: string): Promise<Record<string, unknown>>;
clearBSTokens(): void;
startBSTokenRefresh(): () => void; // returns a cleanup function
attemptSilentAuth(): Promise<boolean>;
}
interface BusinessSuiteTokens {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
id_token?: string;
}
interface VerifiedUser {
sub: string;
email?: string;
name?: string;
picture?: string;
client_id?: string;
role?: string;
}
行为说明
signInWithBusinessSuite生成 PKCEcode_verifier, (中文(简体) ),code_challenge(S/256), (中文(简体) ),state,和OIDCnonce,将其储存在localStorage带10分钟TTL哨,然后设定window.location.href向《争端解决协议》提交的报告/auth/v1/oauth/authorizeURL (中文(简体) ). 返回的"承诺"从不解决——页面通航.exchangeCodeForTokens验证state(CSRF),读取存储的PCE验证器,帖子到/auth/v1/oauth/token,然后JWKS-验证访问符和OIDCid_token非ce(每个OIDC Core § 3.1.2.2的重播保护). 扔出状态不匹配,缺少验证器,非-2xx响应,或非ce不匹配.refreshBusinessSuiteToken员额/auth/v1/oauth/token与grant_type=refresh_token并在返回前通过JWKS验证新的访问符.verifyAccessToken使用缓存 JWKS (jose是因为createRemoteJWKSet与issuer: "<supabase>/auth/v1"财务报告和审定财务报表audience: "authenticated". 仅RS256/ES256.getUserInfo开始/auth/v1/oauth/userinfo与提供的无记名标志 并返回解析的JSON尸体.startBSTokenRefresh立即进行检查,然后每60秒进行一次民意测验,并在到期前5分钟刷新标志. 返回一个停止间隔的清理函数. 总是抓取清理 以避免在挂载/卸载组件(例如AuthProvider)中泄漏定时器.attemptSilentAuth返回时true如果在 localStorage 中已经存在有效的访问令牌,或者有一个可以成功交换的刷新令牌. 回返false否则。 永不抛出——错误被吞入,所以登录UI仍然可以渲染出.clearBSTokens删除bs_access_token, (中文(简体) ),bs_refresh_token, (中文(简体) ),bs_user, (中文(简体) ),bs_id_token从本地Storage。 总是打电话签到.
存储密钥
| 存储 | 密钥 | 目的 | 终身 |
|---|---|---|---|
localStorage | bs_access_token | JWT 访问符 (JWKS- 验证) | 直到刷新/ 签出 |
localStorage | bs_refresh_token | 刷新符号 | 直到刷新/ 签出 |
localStorage | bs_user | JSON 序列化 VerifiedUser | 直到刷新/ 签出 |
localStorage | bs_id_token | OIDC id token (可选) | 直到刷新/ 签出 |
localStorage | bs_oauth_code_verifier | PKCE 代码验证器 | 仅登录入流, TTL 守护 |
localStorage | bs_oauth_state | CSRF 状态 | 仅登录入流, TTL 守护 |
localStorage | bs_oauth_nonce | OIDC 重放保护非ce | 仅登录入流, TTL 守护 |
localStorage | bs_oauth_started_at | PKCE TTL 监视器 | 仅输入流程 |
localStorage | bs_oauth_flows | 国有证券交易 | 十分钟/完成/退出 |
localStorage | bs_oauth_inflight_codes | 具有所有者身份的每个编码索偿 | 十分钟/完成/退出 |
设计说明
- 每个域
localStorage——道具从不离开应用的自产地. 通过OIDC实现跨应用SSOprompt=none静静地重现( s)attemptSilentAuth),非跨域饼干. 堕落者business_suite_authcookie SSO计划被删除了 2025-02-27; 不要重新引入. - JWKS 验证 —— 说
verifyAccessToken获取 Supabase/.well-known/jwks.json一次并缓存它通过jose是因为createRemoteJWKSet. 仅RS256/ES256. - 第256号强制性规定 ——没有隐性流动,没有
plain挑战. - OIDC 非ce 验证 如果
id_token返回时,其nonce在匹配的按国家键交易(或符合资格的遗留交易)中,对索赔进行非ce核查。 缺失或错配的非ce会投出"id token noce错配——可能的回放攻击"(OIDC Core §3.1.2.2). - 托肯过期事件 如果自动更新回路失败, 或删除了兄弟姐妹标签
bs_access_token,包发送 abs-oauth-expiredCustomEvent打开window因此,应用程序可以在令牌被清除之前做出反应(例如显示一面横幅). 原因:network_error, (中文(简体) ),refresh_rejected, (中文(简体) ),cross_tab_logout。 。 。 .
Per- app OAuth 客户端标识
| 应用 | 客户身份 |
|---|---|
| 风险管理7 | 30f76744-3e0b-40bf-abb8-8c587389802e |
| 第80.3号公路 | 5d804d20-cd1b-4724-9107-86d2a9e51e09 |
| 布莱登 | dcb7af18-254a-4946-b94d-5c606b01fc3f |
| 吞吐量 | 35f0db49-ef62-4115-baba-7b961f034cc3 |
| 管道 | da925c19-8f32-40a0-b74d-4eb9540c422f |
相关文件
- 父复方舟认证架构 :
AUTH_CANONICAL.md - 每个应用的整合:每个消费者的
src/lib/business-suite-oauth.ts
许可证
UNLICENSED(只使用内部套件).