他システムが GCU ID プラットフォームに OIDC 連携する際の注意点
対象読者: 社内の別チームが自分のアプリケーションを GCU ID(Keycloak, realm gcu,
https://login.gcu.co.jp)に OIDC で接続しようとしている場合。identity-hub チーム自身の
開発ガイドではなく、連携する側に向けた注意点集。
Client の作成は Identity Console(https://gcu-idp.pages.dev/clients)の自助サービス UI から
自分で行える(SPA/Web/M2M を選び、所属する Organization の管理者権限があればそのまま作成できる —
redirect URI・web origins・backchannel logout URL・post-logout redirect URI もここで設定する)。
Console の Client 管理 UI に出てこない特殊な設定(SAML 等)だけは、引き続き identity-hub チームに
Keycloak 管理コンソール側での手動設定を依頼すること。
Client の種類をどちらにするか
Section titled “Client の種類をどちらにするか”- フロントエンドのみ(SPA・静的サイト): Public client + PKCE(
Client authentication: Off,Standard flow: On,Require PKCE: On,PKCE Method: S256)。client secret を発行しない — 静的サイトには secret を安全に保管する場所がないため、secret を持たせること自体がリスクになる。 - バックエンドを持つアプリ: Confidential client(
Client authentication: On)。secret は バックエンドの環境変数/シークレットストアにのみ置き、フロントエンドに渡さない。
Valid redirect URIs は本番ドメインだけでなく、デプロイ基盤がデプロイごとに別サブドメインを
払い出す場合(例: Cloudflare Pages のプレビューデプロイ)はワイルドカードも登録しておく
(例: https://*.<project>.pages.dev/*)。Web origins は +(redirect URI から自動導出)で
基本的に十分。
重要な注意点: トークン失効時にログイン画面へ正しく戻れるようにする
Section titled “重要な注意点: トークン失効時にログイン画面へ正しく戻れるようにする”OIDC クライアントライブラリ(oidc-client-ts/react-oidc-context に限らず同種のライブラリ全般)は、
バックグラウンドでの自動トークン更新(silent renew)が失敗したときに「エラー」として表面化させる。
これは access token・refresh token の両方が本当に失効した場合にも発生する、普通に起こり得る状態
であり、バグや異常系ではない。
ありがちな実装ミスは、「エラーがあるかどうか」を「ログイン済みかどうか」より先に判定してしまうこと:
// NG: 失効時にここで止まり、ユーザーが自力で復帰できない「詰み」画面になるif (auth.error) { return <ErrorScreen message={auth.error.message} />;}if (!auth.isAuthenticated) { return <LoginScreen onLogin={() => auth.signinRedirect()} />;}トークンが失効して silent renew が失敗すると auth.error がセットされる一方、
auth.isAuthenticated は(期限切れのため)false になる。上の順序だと、本来ただの
「再ログインが必要なだけの状態」が、ユーザーが手動で復帰する手段のないエラー画面として
固定表示されてしまう。
正しい順序: 認証済みかどうかを先に見る。エラーはログに残す程度に留め、画面は常に通常の ログイン画面にフォールバックする。
// OK: 失効時も含めて、未ログイン状態は常に同じログイン画面に落ちるif (auth.error) { console.warn('OIDC auth error (falling back to login screen):', auth.error);}if (!auth.isAuthenticated) { return <LoginScreen onLogin={() => auth.signinRedirect()} />;}LoginScreen 側のボタンが呼ぶ signinRedirect() は毎回フルのインタラクティブログインを
開始するため、silent renew が失敗した理由(refresh token 失効、ユーザーが他端末でログアウト
済み、等)によらず必ず復帰できる。
権限(ロール)の設計: 自分のアプリ用に権限テーブルを作る前に
Section titled “権限(ロール)の設計: 自分のアプリ用に権限テーブルを作る前に”連携するアプリに「閲覧者/管理者」のような権限の区別を持たせたい場合、自前で権限テーブル・
管理 UI を実装する必要はない(ADR 0008)。Identity Console の Client 詳細ページ
(https://gcu-idp.pages.dev/clients/<clientId>)の「ロール」セクションで、自分の Client 用に
好きな名前のロールを追加・削除できる。ロール名は ^[a-z][a-z0-9-]{0,49}$
(小文字・先頭は英字・以降は数字とハイフン)のみ。一度作ったロール名は変更できない
(削除して作り直す)。
ロールをユーザーに割り当てる操作自体は、現時点では Console の UI がまだなく、BFF の API
(PUT /api/console/v1/users/{userId}/clients/{clientId}/roles)を identity-hub チームに
直接呼んでもらうか、UI が揃うのを待つ必要がある(issue #196 のフォローアップ)。
権限情報の確認方法(アプリ側はここを読むだけでよい)
Section titled “権限情報の確認方法(アプリ側はここを読むだけでよい)”Keycloak の realm ロールやクライアントロールはデフォルトで access token の
realm_access.roles / resource_access.<client>.roles に含まれる(追加のスコープ設定不要)。
自分の Client 用に作ったロールもここに同じように載る。デバッグ時は access token の JWT payload
を base64url デコードするだけで中身を確認できる(署名検証はクライアント側の責務ではないので
不要)。例(oidc-client-ts/react-oidc-context の場合):
function rolesOf(user, clientId) { return user?.profile?.resource_access?.[clientId]?.roles ?? [];}
// ログイン後:const roles = rolesOf(auth.user, 'your-client-id');if (roles.includes('admin')) { /* 管理者向けの画面を出す */}反映タイミングの注意: Console でロールを付け外ししても、ユーザーが既に持っている access token にはすぐには反映されない——token が自然に失効し、再ログインかリフレッシュで新しい token が発行されるまでは古いロールのままになる。即時に反映したい場合は、Console から該当ユーザーの セッションを明示的に取り消す(別の BFF 機能)と組み合わせる必要がある。
この仕組みで足りない場合(例: レコード単位の細かい権限制御が必要): このプラットフォームは Keycloak Authorization Services(UMA 2.0、細粒度の resource/policy ベース認可)を一切使っておらず、 現状サポート外。アプリ側で自前のレコード単位認可を実装するか、identity-hub チームに要望を出すこと。
refresh token は access token と違って長期間有効な機密情報なので、デバッグ目的でも 画面に表示する場合は録画・スクリーンショット・画面共有に含めないよう注意すること。