摘要

  • OAuth First-Party Applications revision 04 提议由 HTTPS Authorization Challenge Endpoint 返回授权码、要求原生客户端继续收集用户输入,或命令其切换到浏览器。省去重定向并没有省去信任判断,而是把已安装应用、发布者证据、界面与风险路由变成凭据表面的一部分。
  • auth_session、PKCE 与 DPoP 能把协议连续性约束到客户端实例和密钥,却不能证明二进制确为第一方、用户看见了预期提示、同品牌应用保持一致的安全体验、风险升高时确实发生回退,或受权的外部动作已经完成。

用户始终留在银行应用里。原生界面要求输入一次性验证码,应用把它提交给授权服务器,授权码从后通信道返回。没有浏览器弹出,也没有上下文切换。

但少掉的是一次界面交接,不是信任交接。信任只是从授权服务器控制的网页,移动到了客户端控制的原生表面。

OAuth 2.0 for First-Party Applications revision 04 定义了 Authorization Challenge Endpoint。客户端可把登录提示、签名后的 passkey challenge、MFA code 或部署自定义输入交给它。服务器可发放授权码,以 insufficient_authorization 要求继续收集输入,或用 redirect_to_web 把用户送回浏览器。

该文件是 OAuth 工作组在 IETF stream 中的活跃 Internet-Draft。revision 04 日期为 2026 年 7 月 1 日,将于 2027 年 1 月 2 日失效,目标为 Standards Track。它不是 RFC;现有证据也不证明 IANA 注册已经完成、实现已互通、产品已部署、应用商店覆盖足够、钓鱼风险下降或授权结果成功。

新端点把展示权移给了客户端

传统 authorization-code 流程由授权服务器控制浏览器中输入凭据的页面。新草案允许受信任的原生客户端承担大部分交互。它向 HTTPS 端点 POST 授权参数,也能附带已从用户处收集的信息。端点接受 OAuth 参数和服务器支持的扩展,但有些 Web 参数在原生语境下没有明确意义,处理方式被留在范围之外。

这不是“浏览器安全、原生不安全”的二选一,而是展示权限的迁移。服务器仍判断累计信息是否足以发码;客户端却可能决定提示怎样呈现、哪个原生组件读取输入、错误如何解释、何时调用私有的中间端点。

草案明确不标准化中间请求格式、challenge schema 与步骤顺序,完整互通需要部署 profile。两个系统即便都暴露 authorization_challenge_endpoint,也可能对提示词汇、响应含义、合法步骤与安全暗示有完全不同的理解。

因此审计不能只存 HTTP 状态。应记录应用 build 与安装实例、challenge profile、提示类型、scope、输入类别但不保存秘密值、授权服务器 policy 版本、返回码和下一步指令。授权码证明服务器签发了 grant artifact,却无法还原用户此前看到的画面。

“第一方”是一种关系,不是请求字段

草案把 first-party app 定义为:由与授权服务器相同的实体控制,而且用户也理解它属于同一实体。服务器在继续之前必须核验客户端的 “first-partyness”,但具体核验方法被明确留在规范范围之外。

这是一条诚实的边界。移动平台可提供 OS 或 app-store attestation,服务器也可结合 Attestation-Based Client Authentication 或 Dynamic Client Registration。每份证据却只能回答较窄的问题:哪个平台为哪个软件或密钥作证、哪个注册建立了 client record、哪个 publisher identity 被声明、当时哪项 policy 接受了证据。

企业控制、分发托管、已安装 binary 完整性、客户端持钥与用户的品牌认知不是同一事实。发布者签名正确的应用可能版本过旧或被攻陷;合法 package 也可能显示误导提示;DPoP key 若没有 enrollment 与 instance policy 的支持,也可能属于意外复制的实例;熟悉的品牌表面还能被仿造。

决策收据要把 attestation issuer、freshness、nonce、policy result、publisher、distribution channel 与安装实例分开保存,并与 client_id、auth_session 和授权码区分。“已核验第一方”是依赖若干证据边的结论,不是自证的字符串。

auth_session 是由应用携带的 cookie 式状态

auth_session 是服务器签发给客户端的不透明值,用来把后续请求关联到同一客户端实例。它类似浏览器 cookie,但由原生应用自己保存和提交。任何响应都可能轮换这个值;客户端必须接受替换,在授权码发放后继续保存,并在用户退出应用时丢弃。

服务器必须保证会话唯一;若使用随机串,草案建议至少 256 位熵。它应当把会话绑定到设备,并拒绝来自另一设备的提交。规范没有规定寿命,服务器可因定时、风险或撤销事件而使它失效,客户端不能假定持续时间。

因此连续性是状态机而不是一次字符串相等。审计轨迹应记录 session generation、predecessor、client instance、用户上下文、DPoP key、challenge stage、创建和最后使用时间、轮换原因、退出处理与终态,同时避免把不透明值本身写进普通 analytics。

一个当前有效的 auth_session 仅证明服务器愿意继续某份上下文。它不证明此前每个提示都诚实显示,也不证明同一人完成了每一步,或旧版本应用仍值得信任。遇到 invalid_session,准确结论是延续失败,而不是用户无权,也不是新会话必然不安全。

DPoP 绑定密钥,不绑定发布者和界面

revision 04 建议使用 sender-constrained token,并描述 DPoP 如何贯穿 challenge、code、token 和 resource 交互。若客户端从第一条请求起提交 DPoP proof,服务器可要求后续 challenge 与 token exchange 使用同一公钥,也可把 auth_session 关联到这把 key,并在会话出现时验证私钥持有。

这能关闭重要的 replay 缺口:拿到 session 值或 authorization code 的另一设备,若没有密钥就难以继续。尽管后通信道比浏览器重定向更少暴露 code 与 PKCE verifier,端到端 key binding 仍有价值。

可是 key continuity 不等于 app provenance。DPoP 证明提交者控制对应私钥,并且 proof 符合请求坐标;它不说明是谁批准了 enrollment、哪个 package 渲染屏幕、实例是否持续无害、服务器是否仍认可应用,或用户是否愿意授权这个 scope。

应分别输出 dpop_key_match、app_attestation_accepted、client_registration_active、first_party_policy_passed、prompt_profile_matched 与 user_authorization_satisfied。一个模糊的 “bound client” 会抹掉事故调查最需要的差异。

浏览器回退是安全状态转换

redirect_to_web 表明授权服务器需要重新掌握展示面。它可用于高风险请求、应用不支持的新 authentication method、account recovery 或异常流程。客户端随后在 external user agent 中开始新的 authorization-code flow,服务器还可返回 PAR request_uri。

转换本身有连续性要求。如果最初的原生请求没有 PKCE code_challenge,服务器不得返回 request_uri,否则相当于建立一份没有 PKCE 的 pushed request。浏览器路径还应遵循 RFC 8252 与 RFC 9700 的 native-app 安全实践,并使用 RFC 9207 的 issuer identification 等手段。

风险开关必须有独立收据:记录 policy trigger、risk band、不支持方法或恢复原因,PKCE 是否已建立,issuer 与 request reference,外部浏览器,return URI、state continuity,以及控制权返回后的结果。“打开过浏览器”不证明到达正确服务器;“继续留在原生界面”也不证明无需回退。

客户端若悄悄把 redirect_to_web 变成一次原生重试,就跨越了政策边界。用看似外部的 embedded web view 代替系统浏览器也一样。测试应覆盖从服务器响应、OS browser 选择直到 issuer-bound callback 的完整转换。

多个第一方应用会复制信任决策

草案要求同一服务器支持多个第一方应用时,原生体验必须一致。这不仅是品牌规范。若用户被训练在若干不同样式的原生提示中输入凭据,就更难解释哪一个仿冒面是假的。每套实现还多出 parser、storage path、SDK version、accessibility layer 和失败模式。

共享 SDK 可减少偏差,也形成共同依赖。SDK 的版本、签名与 rollout 都进入授权控制面:它被攻陷时可一致扩散,分叉时又会不一致漂移。“相同体验”应作为安全合同测试,包括提示与来源暗示、秘密输入处理、截屏和辅助功能暴露、回退行为、错误语义、会话存储与 telemetry redaction,而不仅是颜色和排版。

服务器需要一张清单,把每个 client identifier 和 attestation policy 映射到 owning entity、publisher、distribution channel、允许 build、共享 SDK、challenge profile 与退役计划。某个 sibling app 不满足相同控制时,不能仅因同品牌而继承信任。

原生取得授权码仍只是一个坐标

Authorization Challenge Endpoint 最终发放的仍是 authorization code,token endpoint 再产生 access/refresh token。resource server 还可稍后要求 step-up,让客户端携带 acr_values、max_age 和 scope 重新开始 challenge。

每次转换含义不同:code 是短期 grant artifact;PKCE 把它关联到 verifier;DPoP 把 flow 和 token 关联到 key;auth_session 把请求关联到服务器上下文;step-up 参数描述额外认证需要;access token 则在 resource server 的验证规则下表达授权。

它们都不证明业务动作已经发生。资源服务器仍可拒绝、收紧 policy,或接受请求却未完成下游转账;用户也可能关掉应用,payment 可能逆转,credential 可能签发却未安装。最终证据链必须在 OAuth 收据之后另存 resource decision 和现实结果。