クイックスタート:任意の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に記載されている方式 |
環境のハンドルは、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が引き受けます。アプリ名を表示し、環境で許可されたサインイン方法を提示し、初回は同意を求めます。
コールバックを確認する
Usherは、codeとstateを付けてredirect_uriにリダイレクトします。stateが保存したものと一致しない限り、レスポンスを拒否してください。コードは一回限りで、有効期間は5分です。
コードをトークンと交換する
サーバーから、コードとPKCEベリファイアをトークンエンドポイントにPOSTします。
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 | golang.org/x/oauth2と組み合わせたcoreos/go-oidc |
| 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もそのユーザーのことを忘れます。