OIDC 高级接入与能力参考
先按 Code + PKCE 接入。RP 需要其他响应类型、签名消息或原生回调时,再使用本页。配置库前,检查部署环境的 discovery。
响应类型和 grant
Section titled “响应类型和 grant”response_type |
授权响应 | 所需 grant | 前通道 ID Token 哈希 | 刷新能力 |
|---|---|---|---|---|
code |
授权码 | authorization_code |
无前通道 ID Token | 允许 refresh_token 且用户同意 offline_access 时可用 |
id_token |
含获准资料声明的 ID Token | implicit |
两者都没有 | 无 |
id_token token |
ID Token 和 access token | implicit |
at_hash |
无 |
code id_token |
授权码和 ID Token | authorization_code、implicit |
c_hash |
允许 refresh_token 且用户同意 offline_access 时可用 |
code token |
授权码和 access token | authorization_code、implicit |
无前通道 ID Token | 无 |
code id_token token |
授权码、ID Token 和 access token | authorization_code、implicit |
c_hash、at_hash |
无 |
每种所选类型都须显式启用。code 默认使用 query;Implicit 和 Hybrid 默认使用 fragment。六种类型都支持 form_post。Implicit 和 Hybrid 不接受 query。前通道返回 ID Token 时,请求必须提供非空 nonce。
含授权码的流程中,公开客户端必须使用 PKCE S256,机密客户端建议使用。纯 Implicit 忽略 PKCE。纯 Implicit 和携带 access token 的 Hybrid 在同意流程前移除 offline_access。Token 端点返回的 ID Token 包含对应 access token 的 at_hash,不含 c_hash。
| 参数 | 用途 |
|---|---|
client_id、response_type、scope |
指定客户端和流程。scope 必须包含 openid。 |
redirect_uri |
精确匹配已注册回调。 |
state |
将回调关联到本次授权。 |
nonce |
将 ID Token 关联到请求;前通道 ID Token 必须使用。 |
code_challenge、code_challenge_method |
含授权码流程的 PKCE 关联,只支持 S256。 |
response_mode |
按流程限制选择 query、fragment 或 form_post。 |
prompt、max_age |
控制交互和认证时间要求。见交互行为。 |
acr_values |
以空格分隔的认证上下文偏好。允许敏感操作前,检查返回的 acr。 |
id_token_hint |
提供属于此客户端的有效 ID Token。它本身不能建立可复用登录。 |
ui_locales |
偏好的界面语言,支持 en-US 和 zh-CN。 |
request、request_uri |
选择一种签名 Request Object 传递方式。 |
请求没有提供对应参数时,使用 default_max_age 和 default_acr_values。require_auth_time 要求 ID Token 包含 auth_time。托管规则使用对应的 camelCase 字段,见返回规则。
签名 Request Object
Section titled “签名 Request Object”动态注册配置 request_object_signing_alg="RS256",托管 OIDC 规则配置 requestObjectSigningAlg="RS256"。配置所选 RP 密钥来源。下例使用该指南生成的私钥。
保存为 signed-request.mjs。将 CLIENT_ID 和 REDIRECT_URI 设置为已注册的值。ISSUER 默认使用生产 issuer。
import { createHash, randomBytes, sign } from 'node:crypto';import { readFileSync, writeFileSync } from 'node:fs';
const issuer = process.env.ISSUER ?? 'https://oidc.sudomimus.com';const clientId = process.env.CLIENT_ID;const redirectUri = process.env.REDIRECT_URI;if (!clientId || !redirectUri) throw new Error('Set CLIENT_ID and REDIRECT_URI');const now = Math.floor(Date.now() / 1000);const verifier = randomBytes(32).toString('base64url');const state = randomBytes(32).toString('base64url');const nonce = randomBytes(32).toString('base64url');const encode = value => Buffer.from(JSON.stringify(value)).toString('base64url');const header = { alg: 'RS256', kid: 'rp-signing-1', typ: 'JWT' };const payload = { iss: clientId, aud: issuer, iat: now, exp: now + 120, client_id: clientId, response_type: 'code', scope: 'openid email profile', redirect_uri: redirectUri, state, nonce, code_challenge: createHash('sha256').update(verifier).digest('base64url'), code_challenge_method: 'S256',};const input = `${encode(header)}.${encode(payload)}`;const signature = sign('RSA-SHA256', Buffer.from(input), readFileSync('rp-private.pem')).toString('base64url');const request = `${input}.${signature}`;writeFileSync('pending-authorization.json', JSON.stringify({ issuer, clientId, redirectUri, state, nonce, verifier,}), { mode: 0o600, flag: 'wx' });writeFileSync('request.jwt', request, { mode: 0o600, flag: 'wx' });const url = new URL(`${issuer}/authorize`);url.search = new URLSearchParams({ client_id: clientId, response_type: 'code', scope: 'openid email profile', request,}).toString();console.log(url.toString());将用户浏览器跳转到输出的 URL。待完成的请求信息保留在后端。回调时验证 state 和 iss,用保存的 verifier 兑换授权码,再验证 ID Token 的 nonce。示例文件只表示一次请求;实际 RP 需要能隔离并发请求、清理到期数据的存储。
使用 request_uri 时,通过公开 HTTPS URL 提供 request.jwt 的紧凑 JWT 内容。外层发送 client_id、response_type、包含 openid 的 scope 和 request_uri,不再发送 request。已注册的 request_uris 列表非空时,会限制可用引用;URI fragment 不改变获取的引用。
签名对象中的值覆盖其他外层参数。内层提供 client_id 和 response_type 时,必须与外层一致。提供 iss 时必须指向客户端,aud 必须包含 issuer。时间声明也会被验证。不接受未签名、加密或嵌套的 Request Object。不要将 token 端点的客户端 assertion 用作 Request Object,它们的 audience 和内容不同。
签名 UserInfo
Section titled “签名 UserInfo”动态注册配置 userinfo_signed_response_alg="RS256",托管规则配置 userinfoSignedResponseAlg="RS256"。然后调用 discovery 中的 userinfo_endpoint:
curl "$USERINFO_ENDPOINT" \ -H "Authorization: Bearer $ACCESS_TOKEN"响应是 application/jwt,不是 JSON。使用提供方 discovery 中的 jwks_uri 验证 RS256,按精确 kid 选择密钥,并验证 iss、aud 和到期时间。使用资料声明前,将 sub 与 ID Token 的 subject 比较。不要使用应用 access-token JWKS 验证签名 UserInfo。
签名响应按签发时的 scope、策略和用户同意返回资料。它是快照,撤销同意不能收回已交付的 JWT。Accept header 不会覆盖客户端注册的响应格式。
Form Post 回调
Section titled “Form Post 回调”在普通授权请求中加入 response_mode=form_post。例如:
https://oidc.sudomimus.com/authorize ?client_id=example-rp &redirect_uri=https%3A%2F%2Fapp.example.com%2Foidc%2Fcallback &response_type=code &response_mode=form_post &scope=openid &state=<transaction state> &nonce=<transaction nonce> &code_challenge=<S256 challenge> &code_challenge_method=S256HTTP(S) 回调收到的表单 POST 类似:
POST /oidc/callback HTTP/1.1Host: app.example.comContent-Type: application/x-www-form-urlencoded
code=<authorization-code>&state=<transaction-state>&iss=https%3A%2F%2Foidc.sudomimus.com解析表单,再验证已保存请求的 state 和 iss。使用原始回调 URI 和 verifier 兑换授权码。Implicit 或 Hybrid 返回令牌时,先验证 ID Token、nonce 和适用哈希,再接受令牌。
如果用 Cookie 查找待完成的请求,Cookie 必须通过 SameSite=None; Secure 支持跨站 POST。不要因此跳过请求关联验证。原生自定义 scheme 不能接收 Form Post。
原生 OIDC 回调
Section titled “原生 OIDC 回调”为托管原生客户端配置以下规则:
{ "returnMethod": "OIDC", "payload": { "applicationType": "native", "redirectUris": ["com.example.app:/oidc/callback", "http://127.0.0.1:49152/oidc/callback"], "postLogoutRedirectUris": [], "allowedResponseTypes": ["code"], "allowedGrantTypes": ["authorization_code"], "allowedScopes": ["openid", "email", "profile"], "tokenEndpointAuthMethod": "none" }}通过系统浏览器启动 Code + PKCE。用已注册的自定义 scheme 或回环监听器接收响应。验证 state 和 iss 后,使用同一个精确回调 URI 和 verifier 兑换授权码。不要在安装到用户设备的应用中嵌入客户端密钥或平台私钥。
原生 HTTP 回调必须使用 localhost 或回环地址。application_type=native 不接受 HTTPS 回调。精确匹配包含端口;示例不允许任意回环端口。动态注册没有主机的自定义 scheme 时,还需要 sector 文档确定获准的归属。见注册元数据。
提供方支持成对 subject、RS256 签名和上表的响应类型。不支持 OAuth-only response_type=token、匿名动态注册、通过 RAT 更新元数据,或加密的 ID Token、UserInfo 和 Request Object。
第三方发起登录使用 RP 已注册的 initiate_login_uri。RP 接受 GET 和 POST,验证 iss,再启动普通授权请求。Issuer-host WebFinger 位于 /.well-known/webfinger,用于发现 issuer,不应据此假定邮箱域名映射。