O Deitydo 开放平台

接口文档

基于标准 OAuth2 授权码 + PKCE 流程。所有请求使用 HTTPS,API 响应均为 JSON 格式。

HTTP 状态码总览

状态码含义典型场景
200OK请求成功,返回数据
302FoundOAuth 授权重定向,携带 code 或 error
400Bad Request参数缺失/格式错误/授权码无效
401Unauthorized未认证(凭据无效、令牌过期、无权限)
404Not Found用户不存在
409Conflict资源冲突(手机号/邮箱已被注册)
429Too Many Requests触发限流,请稍后重试
500Internal Server Error服务端异常

错误响应格式

所有 API 错误均返回 JSON,结构如下:

错误返回字段

字段类型必填说明
errornumber / string是错误码(用户 API 为整数,OAuth2 端点 /oauth/token 为标准字符串如 invalid_grant)
messagestring否人类可读的错误描述(部分端点)
error_descriptionstring否详细错误说明(OAuth 端点必返回此字段)
// 用户 API 错误示例(整数错误码)
{"error": 40001, "message": "需提供密码及手机号或邮箱"}
{"error": 40105}

// OAuth2 /oauth/token 错误示例(标准字符串错误码,RFC 6749 §5.2)
{"error": "invalid_grant", "error_description": "授权码不存在或已被消费"}
{"error": "unsupported_grant_type", "error_description": "仅支持 grant_type=authorization_code"}
{"error": "invalid_client", "error_description": "客户端认证失败"}

限流策略

注册/登录/刷新端点按 IP + 端点粒度限流,默认 20 次 / 60 秒。超限返回 429 状态码。

第三方应用登录 — 完整调用流程

第三方应用使用自己的登录界面,收集用户的手机号/邮箱和密码,按照以下步骤完成 OAuth2 授权码登录流程。

0 前置准备(一次性)

· 运营者在平台管理后台注册应用 → 获得 client_id + client_secret

· 配置 redirect_uri 白名单(如 https://yourapp.com/callback)

· client_secret 只能保存在应用后端,绝不暴露给浏览器/App

1 用户在第三方应用登录界面输入账号密码 谁调:用户 → 第三方应用

· 第三方应用展示自己的登录界面,用户输入手机号/邮箱 + 密码

· 第三方应用客户端在提交前生成 PKCE 参数:

— 随机生成 code_verifier(43~128 字符)

— 计算 code_challenge = base64url(SHA256_raw(code_verifier))(对 SHA-256 原始 32 字节做 base64url 编码,不可先转 hex)

— 指定 code_challenge_method = "S256"(推荐)或 "plain"

— 对用户密码做 SHA256(64 位 hex 字符串),不要传明文

— 生成随机 state(防 CSRF,建议存 localStorage/session)

记住 code_verifier,第 3 步要用。

2 调用登录 API,获取授权码 谁调:第三方应用客户端

将用户凭据 + PKCE 参数发送到平台,换取授权码(code):

POST /api/login
Content-Type: application/json

{
  "identifier": "13800000000",
  "password": "a1b2c3d4...(SHA256 后的 64 位 hex)",
  "client_id": "cli_abc123def456",
  "redirect_uri": "https://yourapp.com/callback",
  "code_challenge": "vB8rF2kQ...(第 1 步算出的 base64url 值)",
  "code_challenge_method": "S256",
  "state": "rand0mSt4teStr1ng"
}

平台验证凭据 → 返回授权码:

{
  "code": "a1b2c3d4e5f6...",
  "redirect_uri": "https://yourapp.com/callback",
  "transfer_token": "abc123...(跨设备 SSO 中转令牌,EXE → 浏览器场景使用)",
  "state": "rand0mSt4teStr1ng"
}

!code 有效期 10 分钟,一次性使用。此时尚未获得任何 Token,code 不包含敏感信息,可在客户端传输。

3 后端用授权码换取 Token 谁调:第三方应用后端

最关键的一步——从这一步开始必须由后端完成,因为需要用到 client_secret:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=a1b2c3d4e5f6...(第 2 步拿到的 code)
&redirect_uri=https://yourapp.com/callback(必须与第 2 步一致)
&client_id=cli_abc123def456
&client_secret=cs_xyz789secret
&code_verifier=dBjftJeZ4CVP...(第 1 步生成的原始值)

平台验证 client_secret + code_verifier + code 三要素 → 返回:

{
  "access_token": "eyJhbGciOi...(JWT, 含 sub + cid + aud)",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rF8abc123...",
  "scope": "profile"
}

code 已被消费(不可复用),access_token 有效期 1 小时,refresh_token 有效期 30 天。

4 获取用户信息 谁调:第三方应用后端

用第 3 步拿到的 access_token 获取用户资料:

GET /oauth/userinfo
Authorization: Bearer eyJhbGciOi...
{
  "sub": "uuid-of-user",
  "username": "user123",
  "phone": "138****0000",
  "email": "user@example.com"
}

此时登录完成,第三方应用可根据 sub 建立本地用户关联。

5 Token 过期后刷新 谁调:第三方应用后端

access_token 过期(1 小时)后,用 refresh_token 换取新的。支持 JSON 和 form-urlencoded 两种格式:

POST /api/refresh
Content-Type: application/x-www-form-urlencoded

refresh_token=rF8abc123...
{
  "access_token": "eyJhbGciOi...(新 JWT)",
  "token_type": "Bearer",
  "expires_in": 3600
}

新 access_token 仍然携带 cid,可直接用于第 4 步。刷新后旧的 refresh_token 仍然有效。

接口调用顺序总览(推荐路径):

POST /api/login → POST /oauth/token → GET /oauth/userinfo → POST /api/refresh

第 1-2 步在第三方应用客户端完成(收集凭据 + PKCE → 获取 code),第 3-5 步在第三方应用后端完成(code 换 Token → 获取用户信息 → 刷新)。

备选路径(浏览器 SSO):如果应用没有自定义登录 UI,可让用户通过平台授权页完成登录与授权。支持跨应用 SSO:

GET /oauth/authorize → (授权确认页) → 302 code → POST /oauth/token

首次授权:GET 渲染确认页 → 用户同意 → POST 302 重定向带回 code。
SSO 特性:同一设备登录过任一应用后,访问其他应用的 /oauth/authorize — 已授权应用302 重定向直接发码,未授权应用弹出确认页。

OAuth2 端点

POST /oauth/token 授权码换取 access_token(第三方后端调用)

调用方:第三方应用的后端服务(不可在前端暴露 client_secret)。
前置条件:用户已完成登录并授权(/oauth/authorize 或 /api/login),获得 code。
注意:code 为一次性使用(DELETE RETURNING 原子删除),有效期 10 分钟。终端用户所有登录都会产生 code(不管从 /oauth/authorize 还是 /api/login),统一通过本接口换取 Token。

请求体(application/x-www-form-urlencoded)

字段类型必填说明
grant_typestring是固定值 authorization_code(RFC 6749 §4.1.3 强制要求)
codestring是授权码(来自 /api/login 或 /oauth/authorize 回调)
redirect_uristring是必须与 /oauth/authorize 或 /api/login 请求中的 redirect_uri 完全一致
client_idstring是审批通过后获得的 client_id
client_secretstring是审批通过后获得的 client_secret(仅在后端使用)
code_verifierstring是PKCE 原始随机串(43-128 字符),与授权请求中的 code_challenge 配对验证

以上 全部 6 项均为必填。
code_verifier 经 SHA-256 后做 base64url 编码(RFC 7636 §4.2,不可先转 hex)应与授权请求中的 code_challenge 匹配。
若授权请求中 code_challenge_method 为 plain,则 verifier 与 challenge 直接字符串比对。
返回的 JWT 包含 cid(第三方 clientId)+ aud(受众),代表第三方代表用户访问。

成功响应 (200)

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "abc123...",
  "scope": "profile"
}

请求示例

POST http://oauth.deitydo.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://yourapp.com/callback
&client_id=cli_abc123
&client_secret=cs_xyz789
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

错误响应

错误码说明
invalid_clientclient_id 不存在、client_secret 不匹配或应用未审批通过
invalid_grant授权码无效、已过期(>10 分钟)、已被消费、redirect_uri 不匹配或 PKCE 校验失败
invalid_requestcode 缺失或请求参数不合法
unsupported_grant_typegrant_type 不为 authorization_code

redirect_uri 参数使用规范

同一个字段名,不同接口起不同作用——禁止混用:

前缀白名单 /api/login · /api/register · /oauth/authorize

所有登录/注册/授权场景中,redirect_uri 必须以该应用注册的某个回调地址为前缀。
不匹配 → 40003。

强制完全匹配 /oauth/token

必须严格匹配 /partner/apply 注册的白名单回调地址。
不匹配 → invalid_grant。这是 OAuth2 安全边界的核心。

GET /oauth/userinfo 获取用户信息(OAuth2 通道)

调用方:第三方应用后端。
限制:仅接受通过 /oauth/token(授权码流程)签发的 Token(JWT 中 cid 不为空)。

请求头

Header说明
AuthorizationBearer <access_token>

成功响应 (200)

{
  "sub": "uuid",
  "username": "user123",
  "phone": "138****0000",
  "email": "user@example.com"
}

请求示例

GET http://oauth.deitydo.com/oauth/userinfo
Authorization: Bearer {access_token}

错误响应

错误码说明
40103invalid_token — access_token 无效、过期或未通过 OAuth2 流程签发(无 cid)
GET /api/userinfo 获取用户信息(通用通道)

调用方:终端用户的前端 / App。
区别:接受所有有效 Token(含 OAuth2 授权码 Token),不校验 cid。
注意:返回用户自身信息,无 scope 字段,仅供前端渲染用户资料使用。

请求头

Header说明
AuthorizationBearer <access_token>

成功响应 (200)

{
  "sub": "uuid",
  "username": "user123",
  "phone": "138****0000",
  "email": "user@example.com"
}

请求示例

GET http://oauth.deitydo.com/api/userinfo
Authorization: Bearer {access_token}

错误响应

错误码说明
40103invalid_token — access_token 无效、过期或未提供
404user_not_found — 用户不存在

用户 API

POST /api/send-code 发送邮箱验证码

适用场景:邮箱注册 / 邮箱登录前,获取 6 位数字验证码。
限制:同一邮箱 60 秒内只能发送一次,验证码 5 分钟有效。
无需 Token 认证即可调用。

请求体(JSON)

字段类型必填说明
emailstring是接收验证码的邮箱地址

成功响应 (200)

{
  "success": true,
  "message": "验证码已发送"
}

请求示例

POST http://oauth.deitydo.com/api/send-code
Content-Type: application/json

{"email":"user@example.com"}

错误响应

错误码说明
429发送过于频繁 — 60 秒内同邮箱重复请求
500邮件发送失败 — 邮箱服务异常
POST /api/register 注册终端用户

请求体(JSON)

字段类型必填说明
typestring是注册类型:email / phone / wechat(为未来扩展预留,当前仅 email/phone 可用)
usernamestring是用户名(必填,全局唯一,注册后不可通过此接口修改)
phonestring条件必填手机号(type=phone 时必填)
emailstring条件必填邮箱(type=email 时必填)
codestring条件必填邮箱验证码(type=email 时必填,需先调用 /api/send-code 获取)
passwordstring是客户端 SHA256 后的 64 位 hex 字符串
confirm_passwordstring是重复密码,需与 password 完全一致(同为 SHA256 hex)
client_idstring是应用 client_id(必填,注册成功后自动绑定该应用)
redirect_uristring否注册成功后前端返回地址(需通过 client_id 对应应用的白名单前缀校验)

type 为必填项,决定采用哪种注册方式:
· type=email:必须提供 email 与 code(先调用 POST /api/send-code 获取验证码);
· type=phone:必须提供 phone,无需验证码;
· type=wechat:暂未开放,将返回 40005。
username 为必填项,全局唯一(重复将返回 40901)。
password 与 confirm_password 均为客户端 SHA256 后的 64 位 hex,服务端校验两者一致(不一致返回 40006)。
注册接口无需 Token 认证。client_id 为必填项,注册成功后用户将自动与该第三方应用建立授权绑定。
redirect_uri 为可选参数。必须以该应用在 /partner/apply 注册的某个回调地址为前缀(如注册了 https://yourapp.com/reg,则可填写 https://yourapp.com/reg/dashboard),不匹配返回 40003。

成功响应 (200)

{
  "id": "uuid",
  "username": "alice",
  "phone": "13800000000",
  "email": "user@example.com",
  "redirect_uri": "https://yourapp.com/page-a"   // 仅当入参传入时返回
}

请求示例

POST http://oauth.deitydo.com/api/register
Content-Type: application/json

{"type":"email","username":"alice","email":"user@example.com","code":"123456","password":"a1b2c3...","confirm_password":"a1b2c3...","client_id":"cli_abc123","redirect_uri":"https://yourapp.com/page-a"}

错误响应

错误码说明
40001invalid_params — 必填参数缺失(client_id/password/confirm_password)或 password 格式错误
40003invalid_redirect_uri — redirect_uri 不在该应用注册的回调地址白名单内
40004invalid_verify_code — 验证码错误或已过期
40005invalid_type — type 缺失或取值非法(仅支持 email/phone/wechat)
40006password_mismatch — confirm_password 与 password 不一致
40105invalid_client — 提供的 client_id 无效或未审批
40901资源冲突 — 用户名/手机号/邮箱已被注册
POST /api/login 用户登录(返回授权码)

适用场景:原生 App 或自定义 UI,程序化调用登录接口获取授权码,再由后端调用 /oauth/token 换取 Token。
注意:浏览器端登录请使用 POST /api/login(登录成功同样返回授权码)。

请求体(JSON)

字段类型必填说明
typestring是登录类型:email / phone / wechat(为未来扩展预留;wechat 需携带 access_token 走联合登录)
response_typestring否固定值 code(缺省视为 code)
identifierstring条件必填手机号 / 邮箱(type=email/phone 时必填)
passwordstring条件必填客户端 SHA256 后的 64 位 hex(type=email/phone 时必填;type=wechat 由 access_token 替代)
access_tokenstring条件必填跨应用 SSO 令牌(type=wechat 或跨应用登录时提供)
client_idstring是第三方应用的 client_id
redirect_uristring是回调地址,必须以该应用注册的回调地址为前缀(发码目标,必填)
code_challengestring是PKCE code_challenge
code_challenge_methodstring否PKCE 方式,S256(推荐)或 plain(默认 plain)
statestring否透传防 CSRF,原样返回

type 为必填项:email / phone 走密码认证;wechat 需携带 access_token 走联合登录。
无需 Token 认证。
此接口完成认证后按 RFC 6749 §4.1.2 以 302 重定向发码(不再返回 JSON 授权码):重定向到 redirect_uri?code=xxx&state=xxx,授权码通过 Location 查询参数投递,绝不出现在响应体。程序化客户端需以 redirect:"manual" 读取 Location 头。后续步骤:
1) 从 302 的 Location 取出 code
2) 调用 POST /oauth/token(需 client_secret + code_verifier)
3) 获得 access_token + refresh_token

成功响应 (302 Found)

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz123
Set-Cookie: user_token=...; HttpOnly; SameSite=Lax        ← SSO 设备会话
Set-Cookie: transfer_token=...; HttpOnly; SameSite=Lax    ← 跨设备 SSO 中转(原 JSON 字段已废弃)

请求示例

POST http://oauth.deitydo.com/api/login
Content-Type: application/json

{"type":"phone","response_type":"code","identifier":"13800000000","password":"a1b2c3...","client_id":"cli_abc123","redirect_uri":"https://yourapp.com/callback","code_challenge":"...","code_challenge_method":"S256","state":"xyz123"}

错误响应

错误码说明
400invalid_request — 缺参/格式错误:type、client_id、password、redirect_uri、code_challenge 缺失或格式错,或 response_type 非法
400unsupported_response_type — response_type 不为 code
400invalid_scope — scope 非法(仅支持 profile / userinfo / openid)
401invalid_client — client_id 无效或未审批
401invalid_grant — 账户不存在或密码错误 / access_token 无效或已过期
GET / POST /oauth/authorize OAuth2 授权端点(RFC 6749 Authorization Code)

适用场景:浏览器端 OAuth2 授权码流程 — 第三方应用重定向到本端点,平台根据设备会话自动决策。
职责分离:凭证录入通过 POST /api/login;授权决策通过本端点的 POST 提交 consent。
SSO 特性:同一设备登录过任一应用后,访问其他应用的 /oauth/authorize 即可跳过登录,已授权应用静默重定向发码,未授权应用弹确认页。

GET — 授权入口

查询参数

字段类型必填说明
response_typestring是固定值 code
client_idstring是第三方应用的 client_id
redirect_uristring是回调地址,必须以注册的回调地址为前缀
code_challengestring是PKCE code_challenge
code_challenge_methodstring否PKCE 方式,S256(推荐)或 plain(默认)
scopestring否权限范围,默认 profile;仅允许 profile / userinfo / openid,其余返回 invalid_scope
statestring推荐防 CSRF,原样返回

三种分支行为:

① 无会话 → 401 JSON 错误(前端需跳转到 /api/login)

② 已登录 + 未授权 → 200 HTML 授权确认页(表单 POST 到本端点)

③ 已登录 + 已授权(SSO 静默) → 302 重定向到 redirect_uri?code=xxx&state=xxx

请求示例

GET http://oauth.deitydo.com/oauth/authorize?response_type=code&client_id=app_xxx&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&code_challenge=abc123...&state=xyz789

成功响应 — ③ SSO 静默 (302)

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz789

POST — 处理授权确认

表单参数

字段类型必填说明
consentstring是approve(同意)/ deny(拒绝)
client_idstring是第三方应用的 client_id
redirect_uristring是回调地址
code_challengestring是PKCE code_challenge
code_challenge_methodstring否PKCE 方式,S256(推荐)或 plain(默认)
scopestring否权限范围,默认 profile
statestring否防 CSRF,原样返回

请求示例

POST http://oauth.deitydo.com/oauth/authorize
Content-Type: application/x-www-form-urlencoded

consent=approve&client_id=app_xxx&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&code_challenge=abc123...&state=xyz789

成功响应 — 同意授权 (302)

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=AUTH_CODE_xxx&state=xyz789

拒绝授权 (302)

HTTP/1.1 302 Found
Location: https://yourapp.com/callback?error=access_denied&error_description=用户拒绝授权&state=xyz789

错误响应

错误码说明
40001invalid_params — client_id / redirect_uri / code_challenge 缺失
40001unsupported_response_type — response_type 不为 code
40003invalid_redirect_uri — redirect_uri 不在应用注册的回调地址白名单内
40103unauthorized — 未登录,请先通过 /api/login 建立会话
40105invalid_client — client_id 未注册或未审批
POST /api/refresh 刷新 access_token

支持 application/json 和 application/x-www-form-urlencoded 两种 Content-Type。

请求体

字段类型必填说明
refresh_tokenstring是登录或上轮刷新返回的 refresh_token

刷新接口无需 Token 认证,仅凭 refresh_token 即可换取新的 access_token(JWT 含 aud 声明)。

成功响应 (200)

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600
}

请求示例

POST http://oauth.deitydo.com/api/refresh
Content-Type: application/x-www-form-urlencoded

refresh_token={refresh_token}

错误响应

错误码说明
invalid_request未提供 refresh_token 参数
invalid_grantrefresh_token 无效、已被撤销或已过期
POST /api/logout 清除用户 SSO 会话

适用场景:前端 / App 调用,清除 user_token Cookie 以退出 SSO 登录状态。
无需认证:直接调用即可清除 Cookie,后续访问需重新登录。

成功响应 (200)

Set-Cookie: user_token=; Max-Age=0

{
  "message": "已登出"
}

请求示例

POST http://oauth.deitydo.com/api/logout