コンテンツにスキップ

他システムが 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 管理コンソール側での手動設定を依頼すること。

  • フロントエンドのみ(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 と違って長期間有効な機密情報なので、デバッグ目的でも 画面に表示する場合は録画・スクリーンショット・画面共有に含めないよう注意すること。