使用 KAI 验证器登录
KAI 验证器支持从现有网页登录页接续登录,也提供需要接入方适配的原生 OAuth 入口。两种方式最终都保留应用登记的回调、授权码与 PKCE 校验。
两条接入路径
| 路径 | 接入方改动 | 完成方式 |
|---|---|---|
| 现有网页登录兼容 | 保持现有授权 URL、浏览器会话和回调 | 登录页打开验证器;用户批准后回到原浏览器,由原浏览器完成登录、授权与回调 |
| 原生验证器授权 | 应用创建原生授权事务并处理验证器返回的回调 | 验证器显示应用、组织、权限和用途;用户明确批准后,应用收到标准授权码回调 |
兼容路径仍会打开系统网页登录会话。服务端无法保证现有 iOS App 在不更新的情况下完全绕过 ASWebAuthenticationSession,也不能关闭接入方持有的浏览器会话。自动唤起和返回取决于 iOS、关联域名状态及承载浏览器,页面始终保留“打开验证器”和普通登录入口。
现有应用无需改动的兼容路径
继续使用 Discovery 返回的 authorization_endpoint,按授权码与 PKCE发送请求。移动端登录页提供“使用 KAI 验证器登录”。选择后 Auth 创建短期 handoff,只把一次性随机句柄放入验证器链接。
- 句柄绑定原浏览器的签名 Cookie;手机只能审阅并批准,不能取得该浏览器的 Cookie。
- 手机批准后,请回到原 App 或原浏览器。只有原浏览器能完成登录,随后继续原
state、nonce、PKCE 和回调事务。 - 未安装验证器时继续使用密码、验证码或其他可用方式;唤起失败不会自动确认授权。
- 重试、取消、请求过期或更换登录方式都不应绕过原有验证要求。等待页面在后台暂停轮询,回到前台再检查结果。
- 这条路径与二维码扫描使用不同的一次性凭据;handoff 不能替代动态二维码所要求的扫描证明。
POST /api/auth/authenticator/sign-in/handoff 是 Auth 登录页的同源接口,接入方不需要调用。它不接受外部应用用自己的请求伪造原浏览器登录状态。
原生应用接入
原生入口是 KAI 的扩展接口,不是 RFC 9126 PAR。它只接受已登记的 public OAuth 客户端(token_endpoint_auth_method=none),要求授权码模式、准确登记的 redirect_uri、非空随机 state 和 S256 PKCE。保持 issuer、client ID 和回调配置一致,不把客户端 secret 放入 App。
- 应用生成并保留
state、nonce、code_verifier,计算code_challenge。 - 将标准授权查询串提交到同一 Auth issuer 的原生入口。请求不携带现有 Cookie 或 Bearer token。
POST /api/auth/oauth2/native-authorizations
Content-Type: application/json
{
"authorizationQuery": "client_id=YOUR_PUBLIC_CLIENT_ID&response_type=code&redirect_uri=YOUR_ENCODED_CALLBACK&scope=openid%20profile&state=RANDOM_STATE&nonce=RANDOM_NONCE&code_challenge=BASE64URL_SHA256_VERIFIER&code_challenge_method=S256"
}
响应字段:
| 字段 | 类型 | 用途 |
|---|---|---|
id |
UUID string | 本次事务标识 |
expiresAt |
ISO 8601 string | 请求到期时间,当前为创建后 5 分钟 |
launchURI |
string | com.kai.authenticator:/login?handoff=... |
universalLinkURI |
HTTPS URL string | 同一 issuer 的 /authenticator/login?handoff=... |
browserFallbackURI |
HTTPS URL string | 原授权请求的网页登录入口 |
- 打开验证器链接。
code_verifier留在发起应用中,不发送给验证器。验证器需要已登录、已注册且有效的设备,并在本机解锁后审阅请求。 - 验证器展示完整应用、组织、请求权限及用途、项目政策链接。批准和拒绝分别签名,签名绑定当前设备、账号、事务、请求参数和权限快照。权限或安全策略变化会使旧确认失效。
- 验证器打开应用原先登记的回调。成功包含
code、state、iss,拒绝包含 OAuth 错误。应用验证事务和 issuer 后,用原code_verifier在token_endpoint兑换授权码。随后仍需验证 ID token 的签名、iss、aud、exp和nonce。
不要把收到的链接本身视为登录成功,不接受与登记回调不符的 URL,不记录句柄、签名载荷、授权码或完整回调。
需要网页登录的情况
原生入口保留完整安全策略。当前实现遇到账户或组织要求额外的两步验证、请求含 prompt / max_age、非基础 acr_values,以及无法在原生页面满足的组织策略时,要求转入 browserFallbackURI。没有安装验证器、未完成设备注册、请求过期或用户主动选择其他方式时,也应使用浏览器。
本机 Face ID / Touch ID 解锁是设备私钥使用条件,不会被单独冒充成新的服务端第二因子。恢复码也不会被标记成 TOTP、Passkey 或高等级手机批准。接入方请求额外身份验证时继续遵循额外身份验证,不得将浏览器回退当作降低认证要求的机会。
原生授权创建真实的、只用于 OAuth 授权谱系的认证上下文。它不会发放浏览器 Cookie,也不会出现在个人中心的浏览器会话列表。应用授权仍通过相应授权撤销机制管理;更改账号凭据或安全策略仍可使其失效。
验证器接口与上线条件
以下接口仅供 KAI 验证器使用,要求有效原生授权和当前账号名下的有效设备:
POST /api/authenticator/v1/devices/:deviceId/login-handoffs/review,请求{ "handoff": "..." },返回浏览器请求、原生 OAuth 审阅,或kind=browser_required。- 浏览器请求继续使用现有
requests/:requestId/decision签名批准接口;手机不直接调用浏览器 finish。 - 原生 OAuth 使用
POST /api/authenticator/v1/devices/:deviceId/native-authorizations/:id/decision,请求包含decision和signatureDERBase64,响应含completionURI。
服务端 AASA 仅为 /authenticator/login 声明验证器关联,不接管整个授权服务域名。iOS 应用需要携带对应 Associated Domains entitlement;发布后还需考虑 Apple 关联域名缓存。源码测试、模拟器唤起和 HTTP 集成测试不能替代已安装版本在真机上的 Universal Link、系统浏览器返回和真实回调验收。
同机 handoff 不依赖 APNs:它直接打开验证器并获取请求。跨设备通知需单独完成通知权限、APNs token、设备签名注册及投递验收。“已允许通知”不代表“推送已连接”。
恢复码与通知引导
手机验证器是账号的有效验证方式,不需要再设置 TOTP 才能管理它的恢复码。首次完成手机验证器注册时,服务端为该设备生成十个一次性恢复码,App 引导用户截图或保存到登录设备之外。每个恢复码只能作为密码登录后的备用验证步骤,不能代替密码,也不会获得高等级身份认证结果。
恢复码按设备及代次管理。撤销或替换手机验证器时,它的恢复码同时失效;重新生成时,旧一组立即失效。确认保存后,服务端删除可解密副本,只保留验证摘要及消费记录。注册完成请求只能在原注册有效期内重试领取该次注册生成的原始一组,不能读取后来重新生成的恢复码;超过期限可在已登录、已解锁的验证器内通过新设备签名重新获取待保存的一组。
已安装旧版 App 的用户,首次升级到支持推送的版本后,登录并解锁时会看到一次通知引导。如果还有恢复码待保存,先完成恢复码引导。尚未询问权限时,由用户点击后触发 iOS 权限请求;已经拒绝时引导到系统设置;已有权限时直接尝试登记推送。暂不开启通知不影响扫码、前台批准或备用编码,之后可从 App 设置中开启。该引导按功能记录展示状态,不在每次小版本更新后重复弹出。