用户级令牌

授权码 + PKCE 流程,取得代表授权用户的令牌。

Hikarinagi ID 通过 OpenID Connect 授权码流程签发用户级令牌,PKCE 为必填项。应用须先在控制台配置回调地址,配置后方可开通授权码流程并勾选用户级 scope。

使用标准客户端库

Hikarinagi ID 为标准 OpenID Connect 实现,未引入私有扩展。向客户端库提供下列发现文档地址,端点、支持的 scope 与签名密钥即可自动获取。

https://id.hikarinagi.org/oidc/.well-known/openid-configuration
平台客户端库
服务端 Nodeopenid-client
浏览器 SPAoidc-client-ts
iOS / AndroidAppAuth
Gocoreos/go-oidc

以下为该流程的原始 HTTP 交互,供自行实现或排查时参照。

1. 生成 PKCE 参数

Hikarinagi ID 仅接受 S256,不接受 plain。

const bytes = crypto.getRandomValues(new Uint8Array(32))
const verifier = btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

// 保存 verifier,回调阶段需用它换取令牌
sessionStorage.setItem("hikari_verifier", verifier)

2. 发起授权请求

由浏览器导航至授权端点。

https://id.hikarinagi.org/oidc/auth
?response_type=code
&client_id=$CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback
&scope=openid%20status%3Aread%20status%3Awrite%20offline_access
&prompt=consent
&state=$RANDOM_STATE
&nonce=$RANDOM_NONCE
&code_challenge=$CHALLENGE
&code_challenge_method=S256
参数要求说明
response_type必填固定为 code,不支持隐式流
client_id必填控制台中该应用的 client_id
redirect_uri必填必须与控制台登记的某一条完全一致,包括协议、端口、路径与结尾斜杠,不做前缀匹配
scope必填空格分隔。openid 表示同时签发 ID token;offline_access 需与 prompt=consent 同时出现才会签发 refresh token;其余见「权限范围」,且不得超出控制台已勾选的集合
prompt申请 offline_access 时必填取 consent。按 OIDC 规范,prompt 不含 consent 时 offline_access 会被静默忽略,授权仍然成功但不会签发 refresh token
state必填随机值,回调时原样返回,须逐字比对后再继续
nonce申请 openid 时建议随机值,原样写入所签发 ID token 的 nonce 声明
code_challenge必填code_verifier 的 SHA-256 摘要,base64url 编码
code_challenge_method必填固定为 S256,不接受 plain

3. 接收授权响应

用户在同意屏作出决策后,Hikarinagi ID 将响应参数附于 redirect_uri 的查询串返回。

参数要求说明
code成功时返回授权码,仅可使用一次,有效期 60 秒
state总是返回原样回传授权请求中发出的值
error失败时返回access_denied 表示用户拒绝授权;invalid_scope 表示申请了应用未获授权的 scope
error_description失败时可能返回可读的失败原因,仅供排错

4. 发起令牌请求

客户端认证方式须与控制台登记值一致,其余方式将被拒绝。机密客户端默认为 client_secret_basic:

curl -X POST "https://id.hikarinagi.org/oidc/token" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "code=$CODE" \
-d "redirect_uri=https://your.app/callback" \
-d "code_verifier=$VERIFIER"

登记为 client_secret_post 时,凭据改由表单参数传递:

curl -X POST "https://id.hikarinagi.org/oidc/token" \
-d "grant_type=authorization_code" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "code=$CODE" \
-d "redirect_uri=https://your.app/callback" \
-d "code_verifier=$VERIFIER"

公共客户端不持有密钥,仅传递 client_id:

curl -X POST "https://id.hikarinagi.org/oidc/token" \
-d "grant_type=authorization_code" \
-d "client_id=$CLIENT_ID" \
-d "code=$CODE" \
-d "redirect_uri=https://your.app/callback" \
-d "code_verifier=$VERIFIER"
参数要求说明
grant_type必填固定为 authorization_code
code必填回调中拿到的授权码
redirect_uri必填必须与授权请求中使用的那一条完全一致,否则换取失败
code_verifier必填第一步生成的原始随机串,而非其摘要
client_id公共客户端必填机密客户端不在此处传递,改由所登记的客户端认证方式携带
{
"access_token": "eyJhbGciOiJSUzI1NiIs…",
"id_token": "eyJhbGciOiJSUzI1NiIs…",
"refresh_token": "…",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "status:read status:write"
}

5. 访问受保护资源

以 Authorization: Bearer 请求头携带访问令牌。相关端点统一位于 /v3/user/me/** 之下,主体由令牌的 sub 声明确定,不接受在请求中指定用户。

curl -X PUT "https://api.hikarinagi.org/v3/user/me/rates/galgames/1" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"COMPLETED","rate":9}'