快速入门:任意 OIDC 库
Usher 是一个标准的 OpenID Connect 提供商。如果你使用的语言有经过认证的 OIDC 客户端,那么你需要的一切都已齐备:把它指向你的环境的发现文档,并使用带 PKCE 的授权码流程。
设置
| 设置 | 值 |
|---|---|
| 颁发者 | https://<handle>.usherid.com |
| 发现 URL | https://<handle>.usherid.com/.well-known/openid-configuration |
| 流程 | 授权码(response_type=code) |
| PKCE | 必需,code_challenge_method=S256 |
| 范围 | openid profile email offline_access |
| 客户端身份验证 | 按 token_endpoint_auth_methods_supported 中列出的方式 |
你的环境的标识名(handle)可以在 Usher Console 中找到,就在它的颁发者旁边。开发环境会获得一个随机标识名,例如 wise-otter-42;生产环境则使用你选择的标识名。生产环境也可以改为使用你自己的域名,例如 login.acme.com:在你通过 CNAME 记录将该域名指向 Usher 之后,它就是该环境的颁发者。
读取发现文档
你的客户端需要的每个端点和密钥都列在这里。请让你的库来读取它,而不是把 URL 复制到代码中。
curl -s https://acme.usherid.com/.well-known/openid-configuration | jq{
"issuer": "https://acme.usherid.com",
"authorization_endpoint": "https://acme.usherid.com/api/auth/oauth2/authorize",
"token_endpoint": "https://acme.usherid.com/api/auth/oauth2/token",
"userinfo_endpoint": "https://acme.usherid.com/api/auth/oauth2/userinfo",
"jwks_uri": "https://acme.usherid.com/api/auth/jwks",
"introspection_endpoint": "https://acme.usherid.com/api/auth/oauth2/introspect",
"revocation_endpoint": "https://acme.usherid.com/api/auth/oauth2/revoke",
"end_session_endpoint": "https://acme.usherid.com/api/auth/oauth2/end-session",
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
"code_challenge_methods_supported": ["S256"]
}流程
将用户送往授权端点
生成一个随机的 PKCE 验证器并保存起来,同时生成一个随机的 state,两者都要存放在只有这个浏览器会话才能访问的地方(短期有效的 httpOnly Cookie 就很合适)。然后带上 response_type=code、你的 client_id 和 redirect_uri、上面列出的范围、state,以及验证器的 S256 code_challenge 进行重定向。
让用户登录
接下来交给 Lobby:它会写明你的 App 的名称,提供你的环境允许的登录方式,并在第一次登录时征求用户同意。
检查回调
Usher 会带着 code 和 state 重定向到你的 redirect_uri。除非 state 与你保存的值一致,否则请拒绝该响应。授权码只能使用一次,有效期为五分钟。
用授权码换取令牌
从你的服务器将授权码和 PKCE 验证器提交到令牌端点:
curl -s -X POST https://acme.usherid.com/api/auth/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://app.acme.example/callback \
-d code_verifier="$CODE_VERIFIER"验证 ID 令牌
根据 jwks_uri 上的密钥检查它的签名,并确认 iss 是你的颁发者、aud 是你的客户端 ID、exp 尚未到期。你的库会替你完成这些检查;请确认这项功能没有被关闭。
在 Node.js 中
openid-client 会完成以上所有步骤,包括验证:
import * as client from 'openid-client'
// discovery: every endpoint and key comes from the issuer
const config = await client.discovery(
new URL('https://acme.usherid.com'),
process.env.USHER_CLIENT_ID!,
process.env.USHER_CLIENT_SECRET!,
)
// 1. send them to the Lobby, with PKCE (S256) and a state to check later
const codeVerifier = client.randomPKCECodeVerifier()
const state = client.randomState()
const url = client.buildAuthorizationUrl(config, {
redirect_uri: 'https://app.acme.example/callback',
scope: 'openid profile email offline_access',
code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
code_challenge_method: 'S256',
state,
})
// keep codeVerifier and state in a short-lived httpOnly cookie, then redirect to url
// 2. back at /callback: swap the code for tokens
const tokens = await client.authorizationCodeGrant(config, new URL(request.url), {
pkceCodeVerifier: codeVerifier,
expectedState: state,
})
const { sub, email, name } = tokens.claims()!在其他语言中
以下这些广泛使用的库都使用标准的 OpenID Connect,无需任何 Usher 专属的内容即可与 Usher 配合使用:
| 语言 | 库 |
|---|---|
| Python | Authlib |
| Go | coreos/go-oidc 搭配 golang.org/x/oauth2 |
| Java | Spring Security OAuth 2.0 Client |
| .NET | Microsoft.AspNetCore.Authentication.OpenIdConnect |
| Ruby | omniauth_openid_connect |
| PHP | jumbojett/openid-connect-php |
保持登录
使用 offline_access 范围时,令牌响应中会包含一个刷新令牌。当旧的访问令牌过期时,用它配合 grant_type=refresh_token 获取新的访问令牌。刷新令牌会轮换:每次使用都会返回一个新的刷新令牌,旧的随即失效。
退出登录
先清除你自己的会话,然后带上 post_logout_redirect_uri(如果有 id_token_hint,也一并带上)重定向到 end_session_endpoint,让 Lobby 也忘记这位用户。