跳到正文
文档 · 任意 OIDC 库

快速入门:任意 OIDC 库

Usher 是一个标准的 OpenID Connect 提供商。如果你使用的语言有经过认证的 OIDC 客户端,那么你需要的一切都已齐备:把它指向你的环境的发现文档,并使用带 PKCE 的授权码流程。

设置

设置值
颁发者https://<handle>.usherid.com
发现 URLhttps://<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 会完成以上所有步骤,包括验证:

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 配合使用:

语言库
PythonAuthlib
Gocoreos/go-oidc 搭配 golang.org/x/oauth2
JavaSpring Security OAuth 2.0 Client
.NETMicrosoft.AspNetCore.Authentication.OpenIdConnect
Rubyomniauth_openid_connect
PHPjumbojett/openid-connect-php

保持登录

使用 offline_access 范围时,令牌响应中会包含一个刷新令牌。当旧的访问令牌过期时,用它配合 grant_type=refresh_token 获取新的访问令牌。刷新令牌会轮换:每次使用都会返回一个新的刷新令牌,旧的随即失效。

退出登录

先清除你自己的会话,然后带上 post_logout_redirect_uri(如果有 id_token_hint,也一并带上)重定向到 end_session_endpoint,让 Lobby 也忘记这位用户。