公共 SPA
公共 SPA 不持有 client_secret,使用授权码 + PKCE S256。浏览器包只包含 VITE_* 公共参数,
Reader 凭据只存在于 API 进程。
公共参数(构建时注入)
Section titled “公共参数(构建时注入)”VITE_ISSUER=https://id.example.comVITE_CLIENT_ID=your-spa-clientVITE_REDIRECT_URI=https://spa.example.com/callbackVITE_POST_LOGOUT_REDIRECT_URI=https://spa.example.com/VITE_SCOPE=openid profile emailVITE_API_BASE_URL=https://api.example.comAPI 进程参数
Section titled “API 进程参数”API_AUDIENCE=urn:app:productionSPA_CLIENT_ID=your-spa-clientREADER_CLIENT_ID=your-readerREADER_CLIENT_SECRET=issued-reader-secretALLOWED_ORIGIN=https://spa.example.comALLOW_INSECURE_LOOPBACK=falsePORT=3001pnpm installpnpm --filter @yufu/oidc-spa buildpnpm --filter @yufu/oidc-spa start:apipnpm --filter @yufu/oidc-spa dev- SPA 使用
oidc-client-ts发起 code + PKCE;sessionStorage隔离标签页,不启用 iframe 静默续期。 - 回调完成 state / nonce 校验后保存用户;刷新只走 refresh token,本标签页内 single-flight, 避免并发刷新。
POST {API}/api/operation携带Authorization: Bearer <access_token>。- API 先本地校验 RS256、issuer、
aud=API_AUDIENCE、typ=at+jwt、有效期;再用 Reader 凭据内省, 确认active、client_id=SPA_CLIENT_ID、sub与 JWT 一致。 - 退出:本地清用户;中心退出走 RP 发起端点;后通道通知到达时按
sid/sub清缓存。
- 不要在 SPA 中放
client_secret或 Reader 凭据。 - 回调页必须是该 SPA 客户端注册的回调,不要复用 Web 客户端回调。
- Access Token 的
aud是应用环境的 access audience,不是 client id。 - 保护 API 必须校验
typ=at+jwt,ID Token 不能当作 access token 使用。