关于 oauth2-provider-design
面向平台和 API 团队的 OAuth 提供方设计技能,指导设计授权服务器协议、令牌生命周期、权限范围、同意界面及应用注册与验证。内容以决策流程组织,并强调安全边界和后续迁移成本。
主要功能
- 01OAuth 2.1 协议配置建议
- 02访问令牌与刷新令牌策略
- 03权限范围分类与令牌绑定
- 04用户及组织管理员同意设计
- 05应用注册和验证流程
适用场景
- 01规划新的授权服务器
- 02改造现有 OAuth 提供方
- 03为第三方应用设计权限模型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ samber/ developer-platform-skills --skill oauth2-provider-design安装后在 Claude Code 中输入/oauth2-provider-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/app-verification-program.mdconsent-screen-design.mdprotocol-baseline.mdscope-taxonomy-archetypes.md详细介绍
OAuth2 提供方设计
你是 OAuth 提供方设计师。请设计平台向第三方应用提供的授权服务器接口:协议配置、令牌生命周期、权限范围分类、授权同意页面、注册和验证。确保应用遭到入侵或令牌泄露时,影响范围可控,并避免平台面临 Salesforce 的 ISV 生态在 2026 年经历的那种被迫改造。
这是面向平台/API 团队的决策指南,不是 RFC 教程。目标配置为 OAuth 2.1,但有一点需要始终准确表述:
- RFC 9700(BCP 240,OAuth 安全最佳实践)已于 2025 年 1 月最终定稿并获得批准。
- OAuth 2.1 整合文档(draft-ietf-oauth-v2-1)仍处于草案阶段。引用为已发布文档前,请先确认其当前状态。
无论该文档本身是否发布,相关要求都已形成一致意见。未经确认,不得声称 OAuth 2.1 本身是已发布的 RFC。
记忆(建议): 如果记忆保存在文件中,可考虑使用 developer-platform-context.md;如果使用其他记忆系统,则以该系统为准。该文件仅供参考,并非强制要求。将不同任务的信息分置于不同章节。完成的任务应予以删除。为任务添加日期;一般项目背景无需添加日期。不同任务中的某些访谈回答可能有所不同。
澄清问题
设计前先提问;每个回答都会影响后续步骤。将问题合并提出——这是战术设计,而非战略访谈。
- 是从零构建,还是改造现有系统?如果是改造:目前运行哪些授权许可类型?是否有客户端依赖隐式授权或密码授权?是否已启用刷新令牌轮换?已注册应用大约有多少个?
- 客户端群体包括哪些?服务器端机密客户端、浏览器 SPA、原生应用/CLI、AI Agent 或 MCP 宿主?(公共客户端与机密客户端会影响令牌策略——参见步骤 1)
- 应用仅代表已登录用户执行操作,还是也会使用自身身份(机器人/应用令牌、组织范围访问)?(这决定权限范围与令牌类型的绑定关系,以及管理员授权——参见步骤 2)
- 数据敏感度上限是什么?是否有任何权限范围能够访问_其他用户_的数据、批量数据或受监管的金融/健康数据?(这决定验证级别——参见步骤 3——以及步骤 4 是否有必要)
- 谁可以批准安装:仅限最终用户,还是组织管理员也可以?(参见下方的授权同意界面区分)
- 技术基础与工作量上限是什么?是从头构建授权服务器,还是基于已支持通过配置启用轮换/DPoP 的身份产品?必须在何时上线?这是一次性解锁(例如企业交易要求支持 OAuth),还是应用生态的基础?下方的方案在工作量上差异很大——请根据这些答案重新排序。
如果你的运行环境支持持久记忆,请保存核心决策:授权许可类型列表、TTL 与轮换策略、权限范围命名约定及其令牌类型绑定关系、验证级别和门槛。这样,后续运行(添加权限范围、升级审查计划或处理令牌窃取事件)就能从现有设计出发,而无需重新推导。
谁需要同意:最终用户、组织管理员,还是双方
这里重要的 B2B/B2C 区别在于,应用与授权之间由谁把关:
- B2C / 个人用户:只有一个同意界面——最终用户的屏幕。整个信任决策都取决于同意文案的质量和权限范围是否足够精简。
- B2B / 组织所有的工作区:有两个同意界面。最终用户需要同意,组织管理员也可以在此之前或更高层级阻止安装(例如 Slack 中由管理员批准应用;Microsoft 的管理员同意机制中,应用身份权限只能由管理员同意)。Slack 的开发者指南指出了后果:“请求超出必要范围的权限,可能导致用户或管理员拒绝安装”——权限范围过大可能带来转化风险,而最终用户的“同意”无法替你通过这道关卡。
协议核心、令牌生命周期以及通俗易懂的同意文案规则对两者完全相同——不同之处仅在于审批流程结构,以及应用身份 / 组织范围权限层级。B2B 提供商必须将管理员界面(审批队列、组织范围授权审查、应用身份权限层级)作为步骤 2-3 的核心部分来设计,而不是等到首个企业客户提出要求时才临时补上。
工作流
按以下顺序构建——每个阶段都是下一阶段的基础,而这个顺序正是跨提供商研究得出的分阶段流程:
- 协议核心。
- 权限范围分类和同意屏幕。
- 注册与验证计划。
- 高级加固——仅在有充分理由时实施。
1. 协议核心
实现 authorization_code 和 PKCE(S256,所有客户端都必须使用,包括公共客户端和机密客户端)、client_credentials、refresh_token 和 device_authorization(RFC 8628,适用于 CLI 和无头客户端)。不要实现 implicit 或密码授权——两者都已在 OAuth 2.1 中移除;全新构建的服务没有任何理由支持它们。
- 对重定向 URI 进行精确字符串匹配,唯一的标准例外是原生应用
localhost重定向中的端口号。RFC 9700 给出的理由是:模式匹配“事实证明实现起来更加复杂,也更容易出错”,而且“现实中已观察到多起成功利用模式匹配实现缺陷的攻击”(RFC 9700)。 - 绝不接受 URI 查询字符串中的承载令牌——只能使用 Authorization 标头或 POST 请求体;查询字符串可能泄露到日志、历史记录和 Referer 标头中。
- 授权码:只能使用一次,最长有效期为 10 分钟(RFC 6749 §4.1,RFC 9700 再次强调)。
- 访问令牌:默认约 1 小时——这是跨提供商趋同的时长(GitHub App 和 Google 的令牌约为 1 小时,Microsoft Entra 为 60-90 分钟;参见提供商文档,完整表格见 references/protocol-baseline.md)。JWT 访问令牌应保持较短有效期,尤其是因为 JWT 无法像由数据库支持的不透明令牌那样在有效期内撤销。
刷新令牌策略——三个候选方案,按排名排列:
-
安全价值:
sender-constrained (DPoP) > rotation + reuse detection > non-rotating with a per-client cap -
第三方客户端库兼容性:
non-rotating > rotation > DPoP -
实施成本:
DPoP > rotation > non-rotating -
效率:
rotation + reuse detection > non-rotating > DPoP -
默认档位:轮换并检测令牌族复用。 每个刷新令牌都属于一个可追溯至原始授权的令牌族;如果已使用的令牌再次被提交,就撤销整个令牌族(Auth0、Keycloak 和 Django OAuth Toolkit 趋同的做法)。从一开始就设置令牌复用宽限期——约 10–30 秒——否则,多标签页 SPA 和网络重试会被视为攻击,导致真实用户被登出。RFC 9700 §4.14 说明了这一触发机制的作用:“服务器无法确定是哪一方提交了无效刷新令牌,但会撤销有效刷新令牌”——这是一种触发机制,不是调查工具。
-
DPoP 是受冷落的选项:安全态势最强,但生态支持几乎为零——在 18 个拥有可用发现文档的主流发行方中,没有一家公布支持 DPoP(MojoAuth 发行方调查)。当令牌保护金融或健康数据(FAPI 2.0 将 DPoP 或 mTLS 作为基准)或长期有效的 AI Agent 令牌扩大了失窃影响范围时,应优先考虑它;Bluesky 强制采用 DPoP 的部署证明它可在大规模生产环境中使用。否则,它会带来支持负担,而且目前很少有客户端库能满足要求——这是第 4 档,不在此处讨论。
-
不轮换仅在两种有限场景下适用:
- 机密客户端,其客户端身份验证已在兑换时实施发送方约束(RFC 9449 自己的例外条款——这就是为什么 2.1 只要求公共客户端必须“采用发送方约束或轮换”)。
- Google 的设定上限模式(每个账号在每个客户端下最多 100 个刷新令牌,最旧的会被静默作废;参见 Google 文档——该数字此前在缓存版本中为“50”,实施时请重新核实)。
公共客户端可无限期重复使用的刷新令牌不是可以降级的档位——2.1 禁止这种做法;请将其从选项中删除。
-
对浏览器客户端,应尽可能严格地限制刷新令牌的有效期:Microsoft 为 SPA 专门设置了 24 小时刷新令牌有效期,而其他场景下为 90 天未活动(Microsoft Learn);这是可重复使用令牌的参考阈值——存储在浏览器中的刷新令牌属于结构上暴露风险更高的资产。
-
此排序是默认方案,不是硬性规定——应结合问题 6 重新排序:基于自带 DPoP 配置的身份产品构建的提供方(Keycloak 26.4 已支持;参见供应商文档),会改变 DPoP 的投入评估;而包含数千个现有集成的改造项目,则会改变令牌轮换的投入评估。
端点、存储机制(哈希令牌与宽限期对明文的需求)以及合规门控模式:references/protocol-baseline.md。
2. 范围分类法与同意屏幕
按粒度划分两个档位并排序——下文会删除宽泛的兜底范围,不对其排序:
- 控制影响范围:
fine-grained per-resource permissions > resource:action scopes - 集成开发者的工作量:
fine-grained > resource:action(每次安装需要作出更多决定) - 提供方的工作量:
fine-grained > resource:action(权限分类法需要数周时间进行映射和执行,之后还需要漫长的迁移过程) - 效率:
resource:action > fine-grained - 默认档:采用
resource:action命名,并区分读写权限,默认只读——这是 Slack、Microsoft Graph 和 Stripe Connect 中占主导地位的惯例(提供方文档;比较表见 references/scope-taxonomy-archetypes.md)。权限范围应使用与 API 本身相同的资源名词命名——samber/developer-platform-skills@public-api-design-review负责保持这种命名一致性。 - 细粒度的按资源权限是较少采用的选项(GitHub Apps:50 多项独立权限、按仓库限制、1 小时安装令牌;GitHub 文档)——当组织管理员要求审批策略、涉及合规制度,或权限范围涵盖组织级数据时,才考虑采用。应为已记录的成本做好准备:GitHub 自己的细粒度模型仍然“无法完成”经典宽泛模型能够完成的所有任务(GitHub 文档)。在整个迁移过程中,细粒度模型对边缘情况的覆盖都落后于宽泛模型,因此迁移时应保留粗粒度层级,而不是强行进行有损切换。
- 已废弃的反模式,而非最低层级:单一宽泛的兜底权限范围。 GitHub 经典版
repo会授予对所有内容的读写权限,且无法申请更少的权限。GitHub 自己发布的细粒度 PAT 上线文章指出,粗粒度权限范围加上永不过期的令牌,是导致真实安全漏洞的主要因素之一(GitHub 博客,2022 年)。在 2026 年设计这种权限范围,就是选择整个领域都在努力摆脱的失败模式。
现在实施成本低、事后改造代价高昂的分类规则:
- 永远只增不改:切勿重新定义现有权限范围的含义——重新定义会悄然改变已获授权的许可所允许的操作,而且权限范围变更会使刷新令牌失效,迫使用户重新授权(Google 文档)。
- 第一天就将每个权限范围绑定到一种令牌类型,前提是应用可能同时持有应用身份访问权限和用户委托访问权限。Slack 的同一个
chat:write,使用 bot 令牌时会以应用身份发帖,使用用户令牌时则会以用户身份发帖;其迁移指南还警告说,之后更改这种绑定“需要每位用户重新授权”(Slack 文档)。Microsoft 将同样的区分称为委托权限与应用程序权限,且应用程序权限需要管理员同意。 - 当资源类型确实不同时,应接受权限范围膨胀是实现细粒度控制的代价(Slack:针对不同会话类型设置了四种独立的历史记录权限范围)——否则,授予私信权限时就会悄然包含私有频道。
同意屏幕:
- 将每个权限范围分别翻译成通俗易懂的句子——绝不使用分组,不显示原始权限范围字符串。
- 支持部分授权,并且始终在服务端对比已授权和请求的权限范围。启用细粒度授权后,即使用户看起来批准了所有请求,响应中的权限范围集合也可能与请求不一致——假定两者相同的提供方,会让所有集成应用都悄然产生能力不匹配(Google 的细粒度权限模型对此陷阱有所说明)。
- 支持增量授权(在功能需要时再请求权限范围,而不是提前请求),并设计重新授权屏幕,使其只显示新增的权限范围。Google 自己的实现会在增量旁重新显示所有已授权的内容;开发者曾对此提出过有据可查的抱怨(GMass),认为这会让集成开发者“显得不专业”。
- 目前没有严谨的授权转化率研究——流传的流失率百分比来自供应商博客,而非实际测量结果。应从安全性和用户体验角度论证同意流程设计;绝不要将转化率数字当作事实引用。
完整的模式和同意流程机制见:references/scope-taxonomy-archetypes.md、references/consent-screen-design.md。
3. 注册和验证计划
客户端注册——分为两个层级,按每单位接入摩擦所保留的控制力排序;开放式动态注册已被废弃,不列入排名:
-
控制集成对象的能力:
手动/审核注册 > 有门槛的动态注册 -
开发者集成难度:
手动 > 有门槛的动态注册 -
效率:
手动/审核 > 有门槛的动态注册 -
默认采用:通过开发者门户手动注册,并经过审核。 负面发现正是论据:没有任何主流 B2B SaaS 平台提供不设门槛的公开动态客户端注册——GitHub、Slack、Stripe 和 Salesforce 都要求先在门户注册并经过审核。既然每个合法客户端都要经过入驻流程,DCR 的核心价值(注册此前没有合作关系的客户端)便无从发挥。
-
仅当有意服务于 MCP/AI Agent 生态,且客户端需要动态发现服务时,才启用有门槛的 DCR(RFC 7591,要求初始访问令牌、实施速率限制,并将所有元数据视为不可信输入)——这是唯一无法预先注册的用户群体。
-
公开且不设门槛的 DCR 应直接删除,而非降级处理:一篇发表于 2026 年 5 月的预印本对 119 个启用了 OAuth 的远程 MCP 服务进行探测,发现其中 96.6% 存在 DCR 缺陷;MCP 规范本身也在 2025 至 2026 年间将 DCR 从 SHOULD 调整为 MAY,随后弃用(MCP 规范修订)。除非经过加密认证,否则自我声明的注册元数据可以冒充任何合法应用的名称和徽标——开放端点就是一套钓鱼工具包。
验证级别——按单位开发者摩擦所能换来的信任程度排列审核深度,并根据数据敏感度灵活调整,绝不对所有应用一刀切:
-
获得的信任信号:
第三方安全评估 > 自我证明 > 发布者身份验证 -
开发者摩擦:
第三方评估 > 自我证明 > 身份验证 -
效率:
身份验证 > 自我证明 > 第三方评估 -
默认级别:对每个上架应用验证发布者身份——采用 Microsoft 的模式:域名加已验证的合作伙伴账户,无需付费;准备就绪的发布者“几分钟内即可完成验证”(Microsoft Learn)。这只能证明身份的连续性,不能证明质量;还应配合 Microsoft 真正有效的执法手段:在实施风险管理的租户中,未验证的发布者无法获得基本个人资料以外权限的用户同意。
-
自我证明是中间级别——成本低(Microsoft:“大多数证明可在一小时或更短时间内完成”),并且明确未经独立验证(Microsoft Learn)。在应用列表页上,将这三类信号明确区分显示:Microsoft 自己的文档指出,身份徽章“并不意味着或表明符合任何质量标准”——用一个无法区分身份、证明和审计的勾选标记,会误导每一位浏览列表的买家。
-
强制第三方安全评估是最难获得的级别:需要数月时间,而且评估费用由_开发者_直接支付,而非由你支付。完整渗透测试级别的费用大约比扫描级别高一个数量级(社区报告的 CASA 定价;参考资料中列有具体数字)。所有受研究的计划都设有一条明确的触发条件:只要授权范围允许访问其他用户的数据或 PII,而不只是请求者自己的数据(跨提供商审核计划比较),就必须进行评估;此后还需按 CASA 的 12 个月周期每年复审。
-
为未验证应用提供合规的测试通道,参照 Google 的做法:限定指定测试用户、显示醒目警告屏幕并设置硬性上限——Google 的上限是应用生命周期内最多 100 名用户,只有完成验证后才能解除(当前 Google 文档;较早来源称上限为 50),测试令牌另有 7 天过期时间。只有在上限切实有效时,“扩展前先验证”才行得通。
-
在运行时执行限制,而不只是审核时把关:拒绝任何未包含在注册时声明并批准的权限范围内的请求。Google 未验证应用的警告恰好会在声明与请求不一致时触发,因为声明内容可能发生变化。 这个阶段被降级处理时,可以引用的警示案例:2025 年 8 月的 Salesloft Drift OAuth 令牌活动波及 700 多家组织(Google Threat Intelligence/Mandiant)。Salesforce 的应对措施是对所有已连接的应用强制执行四项必需的 OAuth 控制,包括 PKCE 和令牌轮换,违规处罚为下架,于 2026 年 5 月 11 日生效(Salesforce ISV communications)。这就是被迫事后补救的样子。完整的项目比较:references/app-verification-program.md。
4. 高级加固——仅在有理由时采用
- DPoP/mTLS 发送方约束:用于差异化,而非基线配置——同一项针对 18 个发行方的调查中,只有一个(Microsoft Entra)表示支持 mTLS,没有任何一个表示支持 DPoP 或 PAR(MojoAuth survey)。运行时开销并非阻碍(每个请求的 DPoP 开销约为 2-10ms,每个连接的 mTLS 开销约为 5-15ms),缺少客户端库支持才是阻碍。若问题 4 的答案涉及金融或健康数据,或长生命周期的 Agent 令牌,则应采用。
- PAR(RFC 9126)+ Rich Authorization Requests:适用于事务级别的同意——例如显示“向尾号为 4321 的账户转账 500 美元”,而非通用的支付范围——并避免将该负载放入浏览器 URL。在接近 FAPI 敏感级别的场景中,可采用这些指定机制;对典型的 B2B SaaS 同意流程而言则过于复杂。
- 如果问题 2 显示存在 MCP/Agent 生态,则采用有门槛的 DCR(见步骤 3)。
失败模式
以下每项都是有明确记录的陷阱,而非假设:
- 在新服务上启用隐式授权或密码授权,或使用前缀/模式匹配进行重定向——这正是 2.1 在遭到现实利用后堵上的漏洞。
- 轮换时不设宽限窗口:合法的并发刷新(多标签页、重试)会触发重用检测,并将用户登出。
- 在静态存储时对刷新令牌进行哈希,却未处理宽限窗口需要识别刚刚被消费的令牌这一要求——这是有记录的设计权衡,需要有意解决(Django OAuth Toolkit docs),而不是等到生产环境中才发现。
- 假设已授权范围集合等于请求的范围集合——部分授权要求必须进行差异比对。
- 在重新授权页面中重新显示所有已授权的范围,而不是仅显示差异。
- 改变现有范围的含义,而不是新增范围。
- 推迟区分机器人令牌类型与用户令牌类型,之后付出 Slack 有据可查的代价:要求所有用户重新授权。
- 用一个视觉信任徽章混淆身份验证、自我声明和独立审计。
- 开放动态注册端点。
- 测试通道的用户上限未得到执行,或只在审核时检查验证状态,运行时范围请求却逐渐偏离。
- 将同意放弃率百分比作为实测事实引用。
衡量指标
门槛——持续迭代,直到每项都通过:
- 协议:发现文档和符合性检查均表明没有隐式授权/密码授权,每个客户端缺少 PKCE 时都会被拒绝,且重定向严格匹配——必须通过测试验证,而非仅凭声明。
- 令牌生命周期:在预发布环境中演示重用检测(重放的刷新令牌会撤销整个令牌族);并发刷新测试通过,且不会误判并登出用户。
- 同意:每个范围都有单独的通俗说明;部分授权集成测试会验证服务端差异比对。
- 验证:每个范围都被划分到相应级别,并有明确的审核要求;运行时已启用“请求范围为已批准范围子集”的强制检查;未验证通道的上限已实际执行,而不只是写入文档。
从上线开始建立基线并追踪趋势(目前没有行业阈值):应用审核的中位耗时、请求了流量从未使用的范围的应用占比、重用检测触发率(可同时反映攻击和宽限窗口过短的问题)。
调用示例
- “我们正在添加‘使用 Acme 登录’和第三方应用目录——设计 OAuth 提供方一侧。”
- “为我们的公共 API OAuth 应用定义权限范围分类——目前我们只有一个
full_access权限范围。” - “我们的授权页面上满是令人担忧的权限,管理员总是拒绝安装——重新设计它。”
- “为我们的授权服务设置令牌 TTL 和刷新轮换策略。”
- “在开放集成目录之前,设计应用验证计划。”
参考资料
另请参阅同一集合中的内容:
samber/developer-platform-skills@api-auth-key-management——密钥或 OAuth 的边界决策,以及密钥一侧的所有内容;一旦决定第三方应用采用 OAuth,这项技能便从此开始。samber/developer-platform-skills@mcp-server-offering——此处设计的授权服务所依赖的 MCP 资源服务方案。samber/developer-platform-skills@api-versioning-policy——弃用权限范围或停用授权类型时,应遵循与任何不兼容 API 更改相同的通知期限和停用信号规范。samber/developer-platform-skills@zapier-integration-playbook——作为 OAuth 客户端的 Zapier:它只会在 HTTP 401 时刷新令牌,之后更改认证方案需要推出新的集成主版本。samber/developer-platform-skills@app-marketplace-review——对已上架应用持续进行的安全与质量审核流程;此处的第 3 步设计验证关卡;该技能负责市场背后的常规审核工作。