流程总览

只做「用 X2Post 登录」的接入方,可以直接看《OpenID Connect(OIDC)》:在同样的流程里加 scope=openid 与 nonce,换令牌时会额外拿到可离线验签的 id_token。

不用人工审核:动态注册(MCP 客户端用)

MCP 客户端(Claude、Cursor 等)没有「开发者后台」,因此提供一个符合 RFC 7591 的动态注册端点, 它会在发现授权服务器后自动调用:

curl -X POST https://x2post.com/api/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "Claude",
    "redirect_uris": ["http://localhost:33418/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"]
  }'

返回 client_id 后走本页的标准授权码 + PKCE 流程。限制与差别:

  • 只发公共客户端(无 client_secret,必须 PKCE S256);
  • 允许申请的权限固定为面向用户可见的开放接口 scope(不含 openid,即不发 id_token);
  • 回调地址沿用同一套规则:必须是 https://,或本地回环(http://localhost / 127.0.0.1)——桌面客户端的本地回调属于后者;
  • 按来源 IP 限流,且总量有上限;注册出来的应用带「未验证」标记,同意页会明确提示用户这是客户端自动注册、未经站长审核;
  • 需要审核可见、可控的应用仍建议走「资料设置 → 我的应用」自助提交,那条路径的应用名与图标由开发者自己维护。

MCP 客户端接入时会先拿到受保护资源元数据(RFC 9728):https://x2post.com/.well-known/oauth-protected-resource (内容是 MCP 端点、授权服务器地址与可用 scope),未带令牌访问 /mcp 时返回的 401 挑战里也会带上这个地址,客户端据此自动发现并进入上面的授权流程。

绑定资源:RFC 8707 resource(MCP 客户端必须带)

授权请求与换令牌请求都可以带 resource,表示「这枚令牌是给哪个资源用的」; 带了之后令牌会与该资源绑定,资源服务器会校验,令牌不能挪用到别处:

&resource=https://x2post.com/mcp

只接受本站自己的地址(同源、无查询串与片段),其他值返回 invalid_target。 换令牌(含 refresh_token)时若也带了 resource,必须与授权时一致。 MCP 客户端连 https://x2post.com/mcp 时请固定带 resource=https://x2post.com/mcp; 普通第三方接入不带这个参数即可(不绑定资源)。

第一步:把用户送到授权页

GET https://x2post.com/oauth/authorize
  ?response_type=code
  &client_id=<你的 client_id>
  &redirect_uri=<注册过的回调地址,必须完全一致>
  &scope=profile:read%20posts:read
  &state=<随机串,用于防 CSRF>
  &code_challenge=<PKCE challenge>
  &code_challenge_method=S256

要点:

  • redirect_uri 与注册值精确匹配(含协议、域名、路径、端口),不匹配会直接报错、不回跳;
  • 公共客户端(纯前端/移动端)必须使用 PKCE;机密客户端也建议使用;
  • state 请务必校验:回跳时会原样带回。

第二步:换令牌

curl -X POST https://x2post.com/api/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'client_id=<client_id>' \
  -d 'client_secret=<机密应用的 secret>' \
  -d 'code=<授权码>' \
  -d 'redirect_uri=<与授权时一致>' \
  -d 'code_verifier=<PKCE verifier>'

成功响应:

{
  "access_token": "x2o_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "x2r_...",
  "scope": "profile:read posts:read"
}

授权码是一次性的:5 分钟内有效、只能兑换一次;重复使用会返回 invalid_grant,并且我们会在检测到复用时撤销该用户在该应用下的全部令牌(防凭据泄漏)。

第三步:调用接口

curl -H 'Authorization: Bearer x2o_...' https://x2post.com/api/open/v1/me

刷新与撤销

刷新(refresh_token 每次刷新都会旋转,旧的立即失效):

curl -X POST https://x2post.com/api/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=refresh_token' \
  -d 'client_id=<client_id>' \
  -d 'client_secret=<secret>' \
  -d 'refresh_token=x2r_...'

撤销:

curl -X POST https://x2post.com/api/oauth/revoke \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'token=x2o_...' -d 'client_id=<client_id>' -d 'client_secret=<secret>'

错误码

error HTTP 含义 常见原因
invalid_client 401 客户端认证失败 client_secret 错误/已轮换;应用被停用
invalid_grant 400 授权凭据无效 授权码过期或已用过、code_verifier 不匹配、redirect_uri 与授权时不一致、refresh 已失效
invalid_request 400 参数问题 缺少 grant_type / client_id / code 等
unsupported_grant_type 400 不支持的授权类型 目前只支持 authorization_code 与 refresh_token

安全清单

  • 用 state 防 CSRF,用 PKCE 防授权码拦截;
  • client_secret 只放在服务端,不要下发到浏览器;
  • 刷新后请存新的 refresh_token(旧的立刻作废);
  • 用户取消授权后请清掉本地令牌,并引导其重新授权。