跳到正文
MuoCeon 文档中心
第三方 OAuth 接入授权流程

授权码流程

从浏览器跳转到拿到授权码:参数、同意页内部动作、回调校验。

第三方应用取令牌只有一条路:授权码 + PKCE。本页只讲流程的前半段——怎么把用户送到同意页,以及怎么把授权码安全地接回来。

时序

用户浏览器            同意页 (id)                    授权服务器 (server)
    │  GET /authorize      │                              │
    ├─────────────────────>│ 登录态检查(未登录先登录)      │
    │                      │ 签发一次性 id_assertion        │
    │                      │ POST /oauth/preauthorize       │
    │                      ├─────────────────────────────>│
    │                      │<── 应用信息 + consent_ticket ──┤
    │  展示应用名与权限清单   │                              │
    │  点击「同意」          │ 现签新 id_assertion           │
    │                      │ POST /oauth/authorize         │
    │                      ├─────────────────────────────>│
    │                      │<── 重定向地址(JSON)──────────┤
    │  302 redirect_uri    │                              │
    │<─────────────────────┤ code + state + iss            │

第 2 步到第 5 步全部在 MuoCeon 自己的页面与服务里完成,第三方无法干预、也不需要复刻。下面列出它们的真实行为,是为了让你理解「为什么某些参数会失效」以及「错误到底从哪一步来」。

授权请求参数

浏览器跳转到 authorization_endpoint(同意页),携带:

参数必填说明
client_id✅登记得到的客户端标识
redirect_uri建议总是带与登记值精确匹配(回环地址忽略端口)。未带时仅在客户端只有一个可用地址时回退
response_type✅固定 code,其他值一律拒绝
scope可选空格分隔;不传则按客户端登记白名单的默认集合
state强烈建议你会原样收到,用于防 CSRF
code_challenge✅PKCE 挑战值(S256)
code_challenge_method✅固定 S256,plain 被拒
acr_values可选要求的认证强度,多个值按优先级排列,见 逐步认证与提权
max_age可选允许的最大认证年龄(秒);超出则要求用户重新认证
authorization_details可选(第一方)RFC 9396 的 JSON 字符串,见 细粒度授权

示例:

https://id.example.com/authorize
  ?client_id=app_9f3c
  &redirect_uri=https%3A%2F%2Fyour-app.example.com%2Fcallback
  &response_type=code
  &scope=openid%20profile%20email%20offline_access
  &state=k3Jd9x
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

同意页内部发生了什么

了解这四步能解释大多数「莫名其妙」的报错。

  1. 登录态检查:未登录时先跳登录页,登录后带着全部原始参数回到同意页,参数不会丢。
  2. 前置校验:同意页向 /v1/oauth/preauthorize 提交 client_id、redirect_uri、scope 等,服务端校验客户端与回调地址后返回展示素材(应用名、权限清单)和一张不透明票据 consent_ticket。票据有效期 30 分钟,内容对客户端不可见。
  3. 提交决策:用户点「同意」或「拒绝」时,同意页会现签一枚新的身份断言,连同票据一起提交给 /v1/oauth/authorize。服务端同时消费票据与断言(断言只能使用一次,重放会被拒绝)。
  4. 返回重定向地址:/authorize 返回的是 JSON(含目标地址),由前端执行跳转,而不是 302。这是给自家前端用的内部接口,第三方不要直接调用。

认证强度不足时票据不会被消费

如果用户当前认证强度低于请求的 acr_values(或超过 max_age),/authorize 返回 401 INSUFFICIENT_USER_AUTHENTICATION,并且不消费票据。同意页据此把用户送去提权页, 完成后带着原参数回来重试。对第三方而言,这一过程完全透明——你只会看到回调晚一些到达。

回调参数

成功:

https://your-app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=k3Jd9x&iss=https%3A%2F%2Fid.example.com%2Fv1%2Foauth

拒绝:

https://your-app.example.com/callback?error=access_denied&state=k3Jd9x&iss=https%3A%2F%2Fid.example.com%2Fv1%2Foauth

必须做的三件校验:

  1. state 与发起时存的值一致。
  2. iss 与可信 issuer 一致(RFC 9207)。服务器声明了 authorization_response_iss_parameter_supported: true,成功与拒绝响应都会带 iss;缺失即视为异常。
  3. code 立即用于换令牌——授权码一次性、寿命以分钟计,不要先存库再异步处理。

常见拒绝原因

现象原因
同意页直接报错,未回调client_id 不存在、redirect_uri 与登记值不匹配、response_type 非 code、PKCE 缺失或非 S256
回调带 error=access_denied用户点了拒绝
换令牌时 invalid_grant授权码已被使用、已过期,或 redirect_uri/code_verifier 不一致
反复被送去提权页acr_values 要求高于用户可达强度,或 max_age 设得过小

不要做的事

  • 不要实现隐式流程(response_type=token):服务端不支持。
  • 不要在浏览器里保存 client_secret;浏览器端应登记为 web_client(公开客户端,无密钥)。
  • 不要用 state 承载业务数据——它是防 CSRF 的一次性随机值,回调后即作废。

本页目录