端点参考
基础 URL:https://api.nanako.org。同样的路径在 https://www.nanako.org 上也可用。
GET /oauth/authorize
流程的起点。这是一次浏览器跳转,不是你服务端发出的请求。
认证方式: 无。
| 参数 | 位置 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client_id | query | 是 | — | |
redirect_uri | query | 是 | — | 与某个已注册地址逐字节一致 |
response_type | query | 否 | code | 也只接受 code |
scope | query | 否 | openid profile | 空格分隔;请显式传 |
state | query | 否 | 空 | 原样拼回回调地址,不转义 —— 请保持 URL 安全 |
code_challenge | query | 否 | 空 | PKCE |
code_challenge_method | query | 否 | plain | 只有完全一致的 S256 才启用 SHA-256 |
prompt | query | 否 | 空 | none 表示请求一次无界面尝试 |
成功: 302 跳转到 Nanako 的登录与授权页。此时还没有授权码 —— 授权码是在
用户同意之后才到达你的回调地址的。
错误: 在这个端点以 JSON 返回,不会重定向到你的回调地址。见 错误。
prompt=none
请求 Nanako 在不展示任何界面的情况下完成流程。Nanako 会重定向到你的回调地址并带上 错误,而不是渲染页面:
| 情况 | 回调收到 |
|---|---|
| 未登录 | ?error=login_required |
| 已登录,但没有匹配的授权记录 | ?error=consent_required |
| 授权检查本身失败 | ?error=interaction_required |
| 已登录且有匹配的授权记录 | ?code=… —— 不展示授权页 |
以上每种情况都会带回 state。收到这三种错误中的任意一种时,去掉 prompt=none
重试一次,回落到有界面的流程。
这是一次顶层跳转,不能放在 iframe 里:Nanako 在每个响应上都发送
X-Frame-Options: DENY 和禁止内嵌的 CSP,所以某些提供方常见的隐藏 iframe 方案在
这里行不通。
POST /oauth/token
用授权码换取令牌,或刷新已有的一对令牌。
认证方式: client_secret_post;授权码 grant 也可用 PKCE。不支持 HTTP Basic。
Content-Type: application/x-www-form-urlencoded。JSON 请求体不会被读取。
grant_type=authorization_code
| 参数 | 必填 | 说明 |
|---|---|---|
grant_type | 是 | authorization_code |
code | 是 | 来自回调,10 分钟内,未使用过 |
client_id | 是 | |
redirect_uri | 是 | 与 /oauth/authorize 那次逐字节一致 |
client_secret | 条件 | 除非授权码是带 code_challenge 签发的,否则必填 |
code_verifier | 条件 | 授权码是带 code_challenge 签发时必填 |
需要哪种凭证,由授权码当初是怎么签发的决定,而不是由你发什么决定。没走 PKCE 签发的码要 client secret;走了 PKCE 签发的码要 verifier。
grant_type=refresh_token
| 参数 | 必填 | 说明 |
|---|---|---|
grant_type | 是 | refresh_token |
refresh_token | 是 | 最近一次签发的那个 |
client_id | 是 | |
client_secret | 是 | 始终必填 —— 这个 grant 没有 PKCE 分支 |
响应
两种 grant 都返回 200:
{
"access_token": "aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY3zA5bC7dE9=",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "zY9xW7vU5tS3rQ1pN9mL7kJ5hG3fE1dC9bA7zY5xW3=",
"scope": "profile email"
}
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | string | 不透明字符串,44 个字符。有效期 2 小时。 |
token_type | string | 固定为 Bearer |
expires_in | number | 固定为 7200 |
refresh_token | string | 每次调用都是新的,上一个此刻已作废。 |
scope | string | 实际授予的 scope |
没有 id_token。见不支持的能力。
GET /oauth/userinfo
返回已登录用户的资料。
认证方式: Authorization: Bearer <access_token>。前缀区分大小写。
响应: 200。sub 必然存在;其余每个 claim 取决于令牌的 scope,以及用户是否
填过对应的值。
{
"sub": "0198c4a1-7f3e-7b21-9d04-1a2b3c4d5e6f",
"name": "hana",
"picture": "https://cdn.nanako.org/avatars/…",
"email": "hana@example.com",
"email_verified": true,
"phone_number": "+8613800138000",
"phone_number_verified": true
}
| Claim | 所需 scope | 说明 |
|---|---|---|
sub | 无 | 稳定且永久的用户标识。用它作为你记录的主键。 |
name | profile | 昵称;用户可以改 |
picture | profile | 头像 URL;未设置时为空字符串 |
email | email | 账号没有邮箱时整个字段不出现 |
email_verified | email | |
phone_number | phone | 账号没有手机号时整个字段不出现 |
phone_number_verified | phone |
sub 有两种格式较新创建的账号,sub 是 UUID(0198c4a1-7f3e-7b21-9d04-1a2b3c4d5e6f)。早于一次
schema 迁移的账号,保留的是数字形式(42)。两种对各自账号都是稳定且永久的。
请把 sub 当作不透明字符串:不要解析它,也不要假定它是 UUID。
POST /oauth/revoke
撤销一个令牌。
认证方式: client_secret_post。客户端凭证是必填的,因此公开(PKCE)客户端无法
调用这个端点。
Content-Type: application/x-www-form-urlencoded。
| 参数 | 必填 | 说明 |
|---|---|---|
client_id | 是 | |
client_secret | 是 | |
token | 是 | 要撤销的令牌 |
token_type_hint | 条件 | 撤销 refresh token 时必填:refresh_token |
响应: 200 {}。
成功响应并不代表真的撤销了什么。令牌不存在、属于别的应用、或者没带对应的
token_type_hint,三种情况返回的都是同一个 200 {}。前两种是刻意为之 —— 避免泄露
哪些令牌存在 —— 但这也意味着 hint 非常关键。请分两次调用分别撤销 access token 和
refresh token,每次显式带上对应的 hint。
GET /.well-known/openid-configuration
返回提供方元数据。
token_endpoint、userinfo_endpoint 和 revocation_endpoint 上带了一个并不存在的
/api/v1 前缀,三个地址都会返回 404。issuer 也与这份文档实际所在的主机不一致,
一些严格的 OIDC 库会因此直接拒绝整份文档。
不要把客户端库指向这个 URL,请照本页配置端点。
供参考:文档里 scopes_supported 为 openid、profile、email、phone;
response_types_supported 为 code;grant_types_supported 为
authorization_code 与 refresh_token;token_endpoint_auth_methods_supported
为 client_secret_post;code_challenge_methods_supported 为 plain 与 S256。
这五项是准确的。
文档里还有 jwks_uri 和 id_token_signing_alg_values_supported: ["RS256"]。这两项
请忽略 —— 系统不签发 id_token,而那个 URL 上的密钥属于一个不相关的内部集成。