全ユーザーに常時MFAを強制すると、ログインの体感は重くなり離脱も増えます。KeycloakのStep-up認証は、通常操作は軽く、送金や設定変更といった機微操作のときだけ強い認証を要求する仕組みです。本記事ではacr/LoAの設計、Conditional MFA、Max Ageによる再認証、機微APIからのacr要求までを、現場の実装手順に落として整理します。

「全操作にMFAをかけたいわけではない。だが、権限変更や送金だけは、直前に本人がもう一度強く認証していることを確実にしたい」——エンタープライズの認証要件では、この温度差の設計が肝になります。Keycloakはacr(Authentication Context Class Reference)とLoA(Level of Authentication)を軸に、この「操作ごとに要求する認証の強さ」を宣言的に扱えます。本記事では、その仕組みと実装パターンを、Quarkus版(現行)を前提に整理します。

01Step-up認証が解く問題

従来のMFA強制は「ログイン時に一律で二要素」というオール・オア・ナッシングの設計になりがちです。これは体感を重くし、SSO配下の全アプリに影響が波及します。Step-up認証の発想は逆で、ベースラインは軽く保ち、リスクの高い操作に差し掛かったときだけ認証レベルを引き上げます。

これを実現するには、(1)認証の強さを数値で表現する語彙、(2)その語彙をトークンに載せる仕組み、(3)アプリ/APIから「このレベルを満たせ」と要求する経路、の3点が必要です。Keycloakは順にLoAacrクレーム、claims/acr_valuesパラメータで提供します。

02acrとLoAのマッピングを設計する

まず realm の設定で、文字列であるacr値を数値のLoAに対応づけます。acrは任意の文字列(例:silvergold、あるいは12)、LoAは必ず数値です。Keycloak公式のServer Administration Guideにある通り、このマッピングは realm 全体で定義するのが基本で、必要な場合のみクライアント単位で上書きします。ベストプラクティスは realm 側に寄せることです。

現場のコツ:acr値の命名は「後から意味が変わらない」ものにしてください。goldのような相対語は要件変更で意味がずれます。pwd=1 / mfa=2 / webauthn=3 のように、認証手段の強さと1対1で読める語彙にしておくと、監査や横展開で揉めません。数値LoAは飛び番(1,2,3ではなく1,10,20)にしておくと、後から中間レベルを挿入できて安全です。

このacrクレームはアクセストークンとIDトークンの両方に載ります。したがってフロントエンドはIDトークンで、リソースサーバ(API)はアクセストークンで、同じacrを検証できます。

acr / LoA マッピングとトークン反映 Realm設定 pwd → LoA 1 mfa → LoA 2 webauthn → LoA 3 認証フロー Conditional-LoA 条件で分岐 トークン acr = "mfa" (ID / access) クライアント / APIからの要求経路 claims={"id_token":{"acr":{"essential":true,"values":["mfa"]}}} または acr_values=mfa / max_age=秒
図:acr値をLoAに写像し、条件分岐でMFAを差し込み、結果をトークンのacrクレームに反映する。要求はclaims/acr_valuesで行う。

03Conditional MFAで認証フローを組む

Step-upの心臓部は、ブラウザ認証フロー内の Conditional sub-flow です。条件付きサブフローは、先頭に Condition - Level of Authentication オーセンティケータを置き、その条件が成立したときだけ後続のステップ(OTPやWebAuthnなど)を実行します。条件が偽なら、そのサブフロー全体がスキップされます。

典型構成は次のようになります。

Condition - Level of Authentication の設定項目は、AliasLevel of Authentication (LoA)(この条件が担保する数値)、そして Max Age です。要求されたLoAが、そのサブフローの担保するLoA以下で、かつ有効期間内なら再要求せず通します。逆に、要求LoAがこのレベルで、まだ満たしていなければ後続のOTP/WebAuthnを実行します。認証手段の実装内部についてはAuthenticator SPIによる拡張もあわせて設計すると、独自要素も同じLoA体系に組み込めます。

現場のコツ:OTPやWebAuthnを「Required」で常時実行にしてしまうと、それはStep-upではなく通常のMFA強制です。Step-upの本質は Conditional sub-flow に閉じ込めること。OTPが未設定のユーザーへの扱い(登録を促すか、Alternativeで逃がすか)は、条件付きサブフロー内のオーセンティケータのRequirement設定で明示的に決めてください。ここを曖昧にすると「MFA未登録ユーザーが機微操作を素通り」という事故が起きます。

04Max Ageと「認証の鮮度」

Step-upで見落とされがちなのが「いつMFAしたか」です。3時間前にMFAした人が、いまSSOで開いた画面から送金するのを、直前のMFAと同等に扱ってよいかは要件次第です。ここを制御するのが Max Age です。

Keycloakは以前、LoAをユーザーセッションに保存するかどうか(Store LoA in user session)というスイッチで鮮度を扱っていましたが、現在は Max Age による秒数指定へ整理されています(keycloak/keycloak#10205)。挙動の要点は次の通りです。

  • LoA 1 のMax Ageを300秒に設定し、acr要求なしでログイン → LoA 1で認証され、トークンは acr=1
  • 301秒後に再度ログイン要求 → SSOで自動認証はされるが、LoA 1は期限切れ扱いとなり acr=0 が返る。
  • 明示的にLoA 1を claims で要求すると、ユーザーは再度パスワード認証を求められ、acr=1 が返る。

つまり Max Age は「そのLoAをどれだけ新鮮とみなすか」の時間窓です。機微操作向けには短く(例:数分)、通常操作向けには長く、という二段構えが定石です。Max Age=0 にすれば「毎回必ず再認証」となり、送金確認のような操作に使えます。

05クライアントからacrを要求する二つの書き方

アプリ側からレベルを要求する手段は主に二つです。OIDCのclaimsパラメータと、acr_valuesパラメータです。

  • acr_values:認可リクエストに acr_values=mfa のように付ける簡便な方法。ただしこれは「希望(voluntary)」であり、満たせなくても認証は成立し得ます。
  • claims:claims={"id_token":{"acr":{"essential":true,"values":["mfa"]}}} のように essential:true を付けると「必須」になり、満たせなければ認証を通しません。

機微操作のStep-upでは、必ず essential:trueclaims 経由で要求してください。acr_values だけでは「MFAできなかったけどそのまま通った」という抜けが生じ得ます。加えて再認証を強制したい場合は prompt=login を併用します。

現場のコツ:Keycloak 26.1以降、OIDCクライアント側に Minimum ACR value という設定が追加され、そのクライアントへのログインで最低ACRレベルを強制できるようになりました(Keycloak 26.1.0リリースノート)。「このクライアントは常にLoA 2以上」といった要件を、アプリ側のリクエスト実装に頼らず realm 側で担保できます。バージョン依存機能なので、利用中のバージョンで設定項目の有無を必ず確認してください。

06機微APIでの再認証:RFC 9470というゴール

Webのリダイレクトで完結する画面と違い、SPA+APIの構成では「APIが不足を検知 → クライアントに上位認証を促す」経路が必要です。これを標準化したのが RFC 9470(OAuth 2.0 Step Up Authentication Challenge Protocol) です(Bertocci, Campbell著、2023年9月)。

流れはシンプルです。リソースサーバはアクセストークンの acr(および認証の鮮度)が不足していると判断したら、401WWW-Authenticate ヘッダで不足を伝えます。エラーコードは insufficient_user_authentication、パラメータとして acr_valuesmax_age を返せます。

  • WWW-Authenticate: Bearer error="insufficient_user_authentication", error_description="A different authentication level is required", acr_values="myACR"

クライアントはこのチャレンジを受け、要求された acr_values / max_age を認可リクエストに反映して再認証し、新しいトークンで同じAPIを叩き直します。ここで max_age は「アクセストークンに紐づく最後の能動的な認証イベントからの許容経過秒数」を意味します。Keycloak側は前述の claims/acr_values と Conditional sub-flow でこの要求に応えます。トークンの受け渡しや境界をまたぐ委譲が絡む場合は、Token ExchangeOIDCグラントタイプの使い分けもあわせて検討してください。

現場のコツ:API側の検証は「acr値の文字列一致」ではなく「LoA数値での不等号比較(要求LoA ≤ トークンLoA)」で書いてください。文字列一致だと、レベル体系を増やすたびに全APIの分岐を直す羽目になります。あわせてauth_timeクレームで鮮度も検証すると、Max Ageの意図をAPI側でも二重に担保できます。amrクレーム(pwd/otpなど、使われた手段)も監査ログに残しておくと、後の証跡で効きます。

07設計・運用で踏みがちな落とし穴

  • SSOセッションとStep-upの取り違え:SSOで「ログイン済み」でも、要求LoAとMax Ageを満たしていなければ再認証が走ります。この挙動をアプリ側が想定していないと、無限リダイレクトや「なぜか毎回OTP」を誘発します。
  • acr=0の扱い:期限切れやレベル未達で acr=0 が返るケースを、リソースサーバが明示的に拒否できるか確認してください。0を「認証済み」と誤認しないことが肝要です。
  • マルチrealm/ブローカリング環境:外部IdPから来た認証のacrをどう自realmのLoAに写像するかは別途設計が要ります。Identity Brokeringマルチrealm設計と一体で検討してください。
  • バージョン差:Step-up周りは版によって設定名や既定挙動(LoAのセッション保存 vs Max Age)が変わってきた領域です。WildFly版は非推奨・Quarkus版が現行という前提で、必ず利用中バージョンの公式ドキュメントで最終確認してください。

EMWでは、Keycloakをエンタープライズの認証基盤として実構築した経験があります。全ログインを重くせず高リスク操作だけ締める、という現実的な落としどころの設計は、導入事例もあわせてご相談ください。

まとめ

Step-up認証は、「認証の強さ」をacr/LoAという数値語彙で扱い、Conditional sub-flowで差し込み、Max Ageで鮮度を管理し、claims(essential:true)で必須要求する、という4つの部品の組み合わせです。API境界ではRFC 9470のチャレンジで標準的に不足を伝えられます。通常操作は軽く、機微操作だけ強く——この非対称を宣言的に設計できるのがKeycloakの強みです。命名とLoAの飛び番、Max Ageの二段構え、API側の不等号比較、この3点を最初に決めておけば、後の横展開で破綻しません。

参考(一次情報)

Keycloakでの認証基盤設計やStep-up導入の検討は、実構築の経験を踏まえてお問い合わせください。

相談する
← ブログ一覧へ戻る