Keycloakでトークンに何を載せるかは、Client ScopeとProtocol Mapperの設計そのものです。default/optionalの使い分け、audience制御、そしてトークン肥大化の回避まで、実構築の現場目線で整理します。
Keycloakの認証基盤を構築していて、最終的に一番細かく詰めることになるのが「トークンに何を載せるか」です。ユーザーのメールアドレス、所属部署、ロール、そしてトークンの宛先(audience)。これらは魔法のように入るわけではなく、Client ScopeとProtocol Mapperという2つの仕組みで明示的に組み立てられます。本稿では、default/optional client scopeの使い分けから、audience制御、claim設計、そして現場で必ず問題になるトークン肥大化の回避まで、実務目線で掘り下げます。バージョンは現行のQuarkusベース(Keycloak 26系前後)を前提としますが、設定名や既定挙動はバージョンで動くことがあるため、細部は公式ドキュメントでの確認をおすすめします。
01Client Scopeとは「mapperの再利用バンドル」
Client Scopeは、複数のクライアントで共有する設定のかたまりです。公式の定義では「protocol mapperとrole scope mappingを複数クライアントで共有するエンティティ」とされています(Server Administration Guideのソース)。クライアントをClient Scopeに紐付けると、そのScopeに定義されたmapperとrole scope mappingをクライアントが継承します。
ここを最初に理解しておくと設計がぶれません。個々のクライアントにmapperを直接生やすこともできますが、それをやると「profileというclaimセットを10クライアントに同じように入れたい」といった要件で破綻します。共通のclaimはClient Scopeに寄せ、クライアント固有のものだけクライアント側に置く、という切り分けが基本です。
Keycloakには最初からprofile、email、roles、web-origins、acr、basicなどのビルトインClient Scopeが用意されており、それぞれOIDC仕様に沿ったmapperが定義済みです。新規realmを作った直後のトークンにいろいろなclaimが入っているのは、これらが既定でアサインされているためです。
02defaultとoptionalの決定的な違い
Client Scopeには2種類のアサイン方法があります。この違いはトークン設計の根幹なので、正確に押さえてください。
- Default client scope:トークン発行時に常に適用されます。OIDCでは、認可リクエストの
scopeパラメータの値に関わらず、mapperとrole scope mappingが必ず適用されます。 - Optional client scope:OIDC専用で、認可リクエストの
scopeパラメータで明示的に要求されたときだけ適用されます。
公式ドキュメントの例が分かりやすく、クライアントがscope=openid phoneを送ると、トークンにはdefault scope(profileやemailなど)のmapperに加えて、optional scopeとして要求されたphoneのmapperが載ります。openidはすべてのOIDCリクエストで使うメタ値です(Server Administration Guide)。
scopeで明示的に取りに行くので、無関係なトークンは軽いままです。03Protocol Mapperの三本柱:user attribute / role / audience
Protocol Mapperは、Keycloakのユーザーモデル(属性、ロール、グループなど)をトークンのclaimに変換するルールです。種類は多いですが、実務で頻出するのは次の三つです。
- User Attribute Mapper:ユーザープロファイル属性(
department、employeeIdなど)をclaimに投影します。Multivaluedフラグや、JSON型として出すか文字列で出すか(JSON Type)の指定が地味に重要です。バックエンドが型を厳しく見る場合、ここの不一致でパースエラーになります。 - Role Mapper:realm roleやclient roleをトークンに載せます。
realm_access.rolesやresource_access.{client}.rolesの構造は、このmapper(User Realm Role / User Client Role)が作っています。claim名やmultivaluedの扱いを変えると、既存のリソースサーバの認可が壊れるので慎重に。 - Audience Mapper:
audクレームに宛先を追加します。次のセクションで詳述します。
各mapperには「ID Tokenに載せる」「Access Tokenに載せる」「UserInfoに載せる」「Introspectionに載せる」のトグルがあります。これを絞ることも肥大化対策として効きます。たとえばフロントで表示に使うだけの属性はID Tokenだけに載せ、Access Tokenには載せない、という切り分けが可能です。
04audience制御:誰宛のトークンかを明示する
audience(audクレーム)は「このトークンは誰が受け取って検証すべきか」を示します。リソースサーバ(API)側は、自分宛でないトークンを弾くべきで、そのためにaudの検証が必要です。Keycloakのアダプタ/ライブラリには、bearerトークン認証時にトークンのaudにこのクライアント名(resource)が含まれるかを検証するオプションがあり、既定はfalseですがセキュリティ上は有効化が推奨されています(Securing Applications and Services Guide)。audを載せないと、この検証が意味を持ちません。
audをトークンに入れる方法は二つあります。
- Audience Mapper:「Included Client Audience」で既存クライアントのIDを、「Included Custom Audience」で任意の文字列(外部サービス名など)をaudに追加します。宛先が固定的に決まっている場合はこちらが明快です。
- Audience Resolve Mapper:動的にaudを解決します。公式Javadocによれば、これは「ユーザーがclient roleを1つ以上持つ"allowed"なクライアントのclient_idをすべてaudに追加する」mapperです(AudienceResolveProtocolMapper Javadoc)。ビルトインの
rolesclient scopeに含まれており、role設計とaud設計が連動します。
05トークン肥大化の回避:実務で効く順番
ロールやグループが多いエンタープライズ環境では、トークンが数KBに膨らみ、ヘッダサイズ上限やCookie保存で問題になることがあります。Bearerで飛ばすなら8KB前後のHTTPヘッダ上限も現実的な制約です。効果の大きい順に対策を挙げます。
- role claimを絞る:最大の膨張源はたいていロールです。
rolesclient scopeの中身(realm role / client role mapper)で、Access Tokenへの出力を必要なものだけに絞る。全ロールをフラットに載せるのではなく、リソースサーバが本当に見るclient roleだけに限定します。 - Full scope allowedをオフにする:クライアントのadvancedで「Full scope allowed」を無効化すると、role scope mappingで明示的に許可したロールだけがトークンに載ります。既定のオン状態だと、ユーザーが持つロールが芋づる式に入ります。マルチテナントやゼロトラスト志向の設計では、まずここをオフにするのが定石です。
- default→optionalへ移す:一部クライアントしか使わない属性scopeはoptionalへ。全トークンから消えます。
- mapperのトークン出力先を絞る:表示用属性はID Tokenだけ、認可用ロールはAccess Tokenだけ、といった分離。UserInfoエンドポイントに逃がせる情報はトークンから外す手もあります。
ロールをトークンから外して認可を成立させる考え方は、細粒度認可の設計と地続きです。ロール・グループ・属性の設計そのものはロール/グループ/属性の設計で、より踏み込んだ認可はきめ細かい認可(Fine-grained Authorization)で掘り下げています。最小権限の実像についてはIAM最小権限の現実も参考になります。
06claim設計の指針:一貫性と検証性
claim設計で現場が後悔しがちなポイントを整理します。
- claim名を最初に決めて固定する:
departmentかdeptか、ネストするかフラットか。一度リソースサーバが依存すると変更コストが跳ね上がります。命名規約を先に決めてClient Scopeに集約してください。 - 型を明示する:User Attribute Mapperの
JSON Type(String/long/boolean/JSON)を意図通りに設定する。数値フラグを文字列で出してしまい、バックエンドの厳格なデシリアライズで落ちる事故は珍しくありません。 - audは必ず設計対象に含める:audを空のまま運用し、後からリソースサーバでaud検証を有効化しようとして全トークンを直す羽目になる、というのが典型的な負債です。最初からaudを載せておきます。
- sub以外を一意キーにしない前提を崩さない:ユーザー識別は原則
sub。メールアドレスなど可変の属性を主キー代わりにするとフェデレーション時に破綻します。
07Evaluateタブで実物のトークンを検証する
設計したmapperが期待通りに効くかは、頭の中ではなく実物で確認すべきです。KeycloakのAdmin Consoleには、クライアントごとに「Client scopes」タブ配下の「Evaluate」サブタブがあります。ここでユーザーを選び、適用するoptional client scopeを選択すると、生成されるトークンのプレビューと、アプリからKeycloakの認可エンドポイントに送るべきscopeパラメータの値が表示されます(Server Administration Guide)。
Evaluateは、audience resolveの挙動確認にも有効です。あるクライアントをscopeに含めたときに生成されるアクセストークンで、そのクライアントがaudに追加されるか(optional client scopeとしてアサインされている場合)を目視できます。本番でトークンを取ってデコードする前に、ここで「載るべきものが載り、載るべきでないものが載っていない」を確認する習慣をつけると、claim設計のイテレーションが速くなります。
IaCで管理している場合は、この確認をterraform provider(keycloak provider)やAdmin REST APIでの適用後チェックに組み込むと再現性が上がります。REST API経由の構成管理はAdmin REST APIとIaCで扱っています。
—まとめ
トークンに何を載せるかは、Client ScopeとProtocol Mapperという2つの明示的な仕組みで決まります。要点は次の通りです。
- 共通claimはClient Scopeに集約し、常時必要なものだけをdefault、それ以外はoptionalに倒す。
- audは設計の最初から載せ、リソースサーバ側でaud検証を有効化する。role連動ならAudience Resolveが保守しやすい。
- 肥大化対策は「role claimを絞る」「Full scope allowedをオフ」「default→optional」「トークン出力先の分離」の順で効く。
- claim名・型・audを規約として固定し、Evaluateタブで実物を検証してから本番に出す。
EMWではKeycloakでエンタープライズの認証基盤を実構築しており、claim設計やトークン肥大化の解消も含めて対応しています。具体的な事例は導入事例を、複数アカウント/テナントにまたがる統制はマルチアカウント統制をご覧ください。
—参考(一次情報)
- Keycloak Server Administration Guide(公式) — Client Scopes、default/optional、Evaluateタブ
- keycloak/keycloak GitHub — con-client-scopes.adoc — Client Scopeの定義とscopeパラメータ挙動
- AudienceResolveProtocolMapper Javadoc(公式) — audの動的解決の仕様
- Keycloak Securing Applications and Services Guide(公式) — bearerトークンのaudience検証