流程总览
只做「用 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(旧的立刻作废); - 用户取消授权后请清掉本地令牌,并引导其重新授权。