@msdongcha/auth-kit
v0.0.4
Published
Framework-agnostic authentication context, redirect, callback and session coordination utilities.
Readme
@msdongcha/auth-kit
发布
本机发布前先构建:在仓库根目录运行 pnpm --filter @msdongcha/auth-kit build,再进入 packages/auth-kit 运行 npm publish。
包配置不强制开启 provenance,因为本机无法自动生成该证明。从支持 provenance 的 CI 环境发布时,显式使用 npm publish --provenance,并按 npm 要求配置 OIDC 权限。
初始化
pnpm add @msdongcha/auth-kitTokenStore 由业务应用实现。SDK 只通过它读取、轮换和清理 token;不会读取 React、Router、页面状态或浏览器存储。
import { create, type TokenStore } from '@msdongcha/auth-kit'
const tokenStore: TokenStore = {
get: () => {
const accessToken = sessionStorage.getItem('access_token')
const refreshToken = sessionStorage.getItem('refresh_token')
return accessToken ? { accessToken, refreshToken: refreshToken ?? undefined } : null
},
set: ({ accessToken, refreshToken }) => {
sessionStorage.setItem('access_token', accessToken)
if (refreshToken) sessionStorage.setItem('refresh_token', refreshToken)
else sessionStorage.removeItem('refresh_token')
},
clear: () => {
sessionStorage.removeItem('access_token')
sessionStorage.removeItem('refresh_token')
},
}
const auth = create({
authDomain: 'https://iam.example.com',
app: 'business-web',
tokenStore,
})authDomain 必须是 HTTPS;仅当 runtime 为 development 或 test 时允许 localhost HTTP。app 是认证入口使用的应用标识。fetchImpl 用于注入 fetch 实现或测试替身。
不要配置 allowedCallbackOrigins。回跳地址的白名单只由 IAM 服务端配置和校验,SDK 不在客户端复制这项授权决策。
create(config) 返回的客户端
validate()
校验认证域名及当前 runtime 是否允许该 URL。
const result = auth.validate()
if (!result.ok) console.error(result.error)buildAuthorizeUrl(options?)
创建 IAM 登录地址,只输出 app、return_to 和可选的 state 参数。
const result = auth.buildAuthorizeUrl({
returnTo: 'https://business.example.com/orders',
state: 'csrf-state',
})
if (result.ok) window.location.assign(result.value)显式传入 returnTo 时优先使用。浏览器中省略它时,SDK 使用当前页面的绝对 URL,并删除查询或 hash 中的 access_token、refresh_token、code、id_token 和 token 参数。非浏览器环境必须显式传入有效的 returnTo,否则返回 RETURN_TO_UNAVAILABLE。SDK 只校验 URL 格式与 runtime;最终回跳是否允许由 IAM 服务端决定。
appCode、clientId 和 callbackUrl 仍是迁移期的兼容别名,分别归一化为 app 和 returnTo;SDK 不会输出 client_id 或 callback_url。
login(username, password)
向 POST /api/v1/auth/token 发送 { username, password },将返回的 token 写入 TokenStore,并返回 token、可选的可信上下文和身份信息。
从 0.0.4 开始,调用方传入原始密码,SDK 在发送请求前将其按 UTF-8 → 标准 Base64(保留 = padding,非 Base64URL)编码为请求的 password 字段。不要在调用方预先编码;SDK 不会猜测密码是否已经编码,也不会裁剪空白或规范化原始密码。用户名保持原样。
例如 auth.login('alice', 'secret') 的请求体是 { "username": "alice", "password": "c2VjcmV0" }。后端必须先对 password 做 Base64 解码并按 UTF-8 还原,再校验原始密码;升级须与后端协议同步,仍接收原始密码的旧后端不兼容此版本。Base64 是编码,不是加密,生产环境仍须使用 HTTPS。此变更仅作用于 login(),不包含注册、重置密码或 SSO。
const result = await auth.login('alice', 'secret')
if (!result.ok) throw new Error(result.error)refreshToken()
使用 TokenStore 中的 refresh token 调用 POST /api/v1/auth/refresh。同一客户端实例中的并发刷新会合并为一次请求;成功后轮换 token,失败时清空 token。
const result = await auth.refreshToken()
if (!result.ok) window.location.assign('/login')getIdentity()
从 TokenStore 读取 access token,以 Bearer token 调用 GET /api/v1/auth/me,返回 IAM 远程响应中的 user、roles 和 permissions。SDK 不使用 JWKS,也不在本地解析 token。
const result = await auth.getIdentity()
if (result.ok && result.value.permissions.includes('orders:read')) {
// 渲染订单入口
}getContext()
同样通过 GET /api/v1/auth/me 获取远程数据,但只在响应包含可信的 principal_kind、selected_tenant、default_target 和 app_access 时返回 AuthContext。缺少这些字段时返回 AUTH_CONTEXT_MISSING;app_access.allowed 为 false 时返回 APP_ACCESS_DENIED。不会从 roles 或 memberships 推断上下文。
const result = await auth.getContext()
if (result.ok) console.log(result.value.selectedTenant)getMemberships()
使用 TokenStore 中的 access token 调用 GET /api/v1/auth/memberships,返回 camelCase 的租户成员列表。SDK 会保留 membershipStatus;列表中的成员关系不代表服务端一定允许切换。
const result = await auth.getMemberships()
if (result.ok) {
for (const membership of result.value) {
console.log(membership.tenantId, membership.isDefault)
}
}switchTenant(membershipId)
使用当前 access token 调用 POST /api/v1/auth/switch-tenant。服务端返回完整的新 access token 和 refresh token 后,SDK 才会原子替换 TokenStore;请求失败或响应不完整时保留原会话。
同一客户端实例中,对相同 membership 的并发切换会合并为一次请求;切换未完成时请求其他 membership 会返回 TENANT_SWITCH_IN_PROGRESS。
const result = await auth.switchTenant('membership-id')
if (result.ok) {
console.log(result.value.identity?.roles)
}logout()
如果 TokenStore 中存在 refresh token,则调用 POST /api/v1/auth/logout 撤销它,请求体为 { refresh_token }。无论服务端请求成功或失败,SDK 都会清空本地 TokenStore;请求失败时返回 LOGOUT_FAILED。没有 refresh token 时只执行本地清理。
const result = await auth.logout()
if (!result.ok) console.error(result.error)其他公开工具
sanitizeCallbackUrl(url)
返回已删除认证参数的路径、查询和 hash,适合回调页在写入 TokenStore 后清理浏览器地址。
window.history.replaceState(null, '', sanitizeCallbackUrl(window.location.href))resolveTenantDecision(input)
优先消费 AuthContext 中受信任的 selected tenant。只有显式设置 allowLegacyDefault: true 时,才兼容一个激活的 legacy isDefault membership。
const decision = resolveTenantDecision({ context: authContext })
if (decision.ok) useTenant(decision.value.selectedTenant.id)createAuthKit(config)
旧版兼容入口。它返回 validateConfiguration()、buildAuthorizeUrl()、decideDestination(context) 和 applySession(result, adapter?)。新业务使用 create();此入口仅保留给仍依赖旧配置和返回形状的调用方。
createRefreshCoordinator()
创建旧版 refresh single-flight 协调器。refresh(task) 会让同一实例中并发的布尔刷新任务共用一个 Promise。
const coordinator = createRefreshCoordinator()
await coordinator.refresh(() => refreshBusinessSession())公开类型与结果
AuthKitConfig、AuthClient、TokenStore、TokenSet、AuthIdentity、AuthContext、TenantMembership、AuthKitResult、LoginResult、AuthorizeOptions、TenantDecision 和相关 legacy 类型均从包根导出。由 create() 返回的客户端方法返回 { ok: true, value } 或 { ok: false, error, retryable? };调用方应先判断 ok 再读取 value。
SDK 可以解包 IAM 的 { success, message, data } 响应信封,也兼容裸 JSON 响应。它不实现 IAM 后端授权、回跳白名单或 JWKS token 校验。
