OAuth 接入流程
本文档介绍如何将你的应用接入 CaelLabID OAuth 2.0 认证。
整体流程
用户浏览器 → 你的应用跳转到 CaelLabID
→ 用户登录并授权
→ CaelLabID 重定向回你的回调地址(带 code)
→ 你的应用用 code 换 token
→ 你的应用用 token 获取用户信息
第一步:引导用户授权
构造授权 URL,让用户浏览器跳转到 CaelLabID:
https://id.caellab.com/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_CALLBACK_URL
&scope=openid
&state=RANDOM_STRING
请求参数
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
response_type | string | 是 | 固定值 code |
client_id | string | 是 | 你的应用 Client ID |
redirect_uri | string | 是 | 回调地址,必须与注册时填写的一致 |
scope | string | 否 | 权限范围。默认 openid,可选 openid profile |
state | string | 推荐 | 随机字符串,用于防止 CSRF |
code_challenge | string | 视应用配置 | PKCE 挑战值,43-128 位 unreserved 字符 |
code_challenge_method | string | 视应用配置 | 固定 S256 |
验证规则
response_type必须为code,否则返回 400client_id必须在系统中已注册且状态为启用redirect_uri必须在应用注册时的回调 URL 列表中scope中不支持的值会被自动过滤,仅保留openidcode_challenge需为 43-128 位 unreserved 字符(A-Z a-z 0-9 - . _ ~),code_challenge_method只接受S256- 应用开启了「强制 PKCE」却没带
code_challenge时返回 400
行为
| 条件 | 行为 |
|---|---|
| 用户未登录 | 存储 OAuth 请求到 Session → 跳转登录页 → 登录后返回授权页 |
| 用户已授权且 scope 足够 | 直接发放授权码 → 重定向到 redirect_uri(免确认) |
| 用户未授权或 scope 不足 | 显示授权确认页 |
已授权免确认
如果用户之前已授权过且当前请求的 scope 是之前已授权 scope 的子集,则自动发放授权码,不再弹出授权页面。
PKCE(推荐开启)
新建的应用默认开启「强制 PKCE」,公共客户端(桌面 App / SPA / CLI)无论开关如何都强制。这类请求必须携带 code_challenge:
// 每次授权都重新生成:43-128 位随机字符串,只存在你自己的服务端
$verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$_SESSION['pkce_verifier'] = $verifier; // 回调换 Token 时要用
// 授权链接上带的是它的 SHA-256 摘要(base64url,去掉末尾的 =)
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
code_challenge 可以公开,code_verifier 不行——不要写进授权链接、前端代码或日志。换 Token 时带上 code_verifier,服务端比对不通过就不发 Token,授权码在路上被截获也换不出东西。
第二步:处理回调
用户同意授权后,CaelLabID 会 302 重定向到你的 redirect_uri:
成功:
YOUR_CALLBACK_URL?code=AUTHORIZATION_CODE&state=RANDOM_STRING
用户拒绝:
YOUR_CALLBACK_URL?error=access_denied&state=RANDOM_STRING
你的处理逻辑:
- 验证
state参数与第一步发送的一致(防 CSRF) - 提取
code参数 - 如果有
error参数,处理用户拒绝授权的情况
第三步:换取 Token
用授权码换取 Access Token。这一步是服务端对服务端的调用,不能在浏览器端执行。
POST /oauth/token
方式一:POST body 传参
POST https://id.caellab.com/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_CALLBACK_URL
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&code_verifier=YOUR_CODE_VERIFIER
方式二:HTTP Basic Auth
POST https://id.caellab.com/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(client_id:client_secret)
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_CALLBACK_URL
请求参数
| 参数 | 必需 | 说明 |
|---|---|---|
grant_type | 是 | 固定值 authorization_code |
code | 是 | 授权码 |
redirect_uri | 是 | 必须与授权请求中一致 |
client_id | 是 | Client ID(Basic Auth 时可省略) |
client_secret | 机密客户端必需 | Client Secret(Basic Auth 时可省略);公共客户端不校验 |
code_verifier | 授权时带了 code_challenge 就必需 | PKCE 验证值,与生成 code_challenge 时用的是同一个 |
成功响应
{
"access_token": "a1b2c3d4...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "e5f6g7h8...",
"scope": "openid"
}
| 字段 | 说明 |
|---|---|
access_token | 访问令牌,用于调用 API |
token_type | 固定值 Bearer |
expires_in | Access Token 有效期(秒) |
refresh_token | 刷新令牌,用于获取新的 Access Token |
scope | 实际授予的权限范围 |
安全注意
redirect_uri 参数必须与授权请求中完全一致,否则会被拒绝。
第四步:获取用户信息
使用 Access Token 调用用户信息接口:
GET https://id.caellab.com/api/userinfo
Authorization: Bearer ACCESS_TOKEN
响应示例
{
"sub": "9402013ac8b214059694f64f130da6f2a9a4603e06215f2c6998929eb511ce08",
"id": "9402013ac8b214059694f64f130da6f2a9a4603e06215f2c6998929eb511ce08"
}
返回字段取决于你的 Scope:
| 字段 | 需要 Scope | 说明 |
|---|---|---|
sub, id | openid | 应用级用户标识 |
display_name, avatar | profile | 昵称、头像 |
跨应用隐私保护
sub 是应用级别的用户标识。同一用户在不同应用中会获得不同的 sub 值。这是设计如此,不是 bug。请勿尝试通过 sub 跨应用关联用户。