メインコンテンツまでスキップ

v0.13.0 影響確認

本ドキュメントは v0.13.0 に含まれる破壊的変更・挙動変更と、その利用者側(RP / テナント運用者)への影響をまとめる。

リリースに向けて対象変更が確定し次第、本ドキュメントへ追記していく。各変更は独立したセクション(## N. <変更>)として追加し、あわせて下記「影響まとめ」表に 1 行加える。

トリアージ完了(2026-08-25 時点)

v0.12.0(2026-07-06 リリース)以降のマージ 39 件をトリアージし、🔴 破壊的 / 🟡 挙動変更 / 🔒 要点検 に該当するものを本ドキュメントへ登録した。🟢 追加・⚙️ 内部のみの変更は「付録: 登録不要と判定した変更」に一覧で残してある(「未対応」と「そもそも無い」を区別するため)。

洗い出しコマンドは git log v0.12.0..main --merges。トリアージの経緯は Issue #1797

アップグレード前に必ず読むもの: 3.1 iat / nbf の 60 秒上限4. PAR 強制6.2 federation 設定更新の全置換化8.3 DBマイグレーション

影響まとめ

#変更種別対象
1http_requests(複数リクエスト)実行時に、response_resolve_configs で解決したステータスが 400 / 500 に丸められなくなった429 / 503 等がそのまま返る)🟡 挙動変更execution.function: http_requests かつ response_resolve_configs を設定している構成
1フェデレーションの callback も同様に丸めなくなった(userinfo / token リクエスト / JWKS 取得の失敗)🟡 挙動変更全フェデレーション構成(standard / facebook / oauth-extension
1standard / facebook$.http_request.response_headers.*配列から文字列に変わった(oauth-extension と統一)🟡 挙動変更userinfo_mapping_rules でレスポンスヘッダーを参照している構成
1standard / facebook のステータスコードのキーが staus_codestatus_code(旧キーは削除)🔴 破壊的userinfo_mapping_rulesstaus_code を参照している構成
2external-api-authentication の認証結果に interaction ごとの内訳(interactions)が追加された🟢 追加認証結果を参照する構成(authentication-policy / /authentication-status のレスポンス)
2ciba-delegation テンプレートの2要素目が端末承認なしで成立する設定だったため修正ciba_start を3回呼ぶだけで通った)🔒 要点検config/templates/use-cases/ciba-delegation/ をベースに構築しているテナント。更新しても既存の設定は変わらないため、手動での修正が必要
3client_assertion / Request Object / DPoP proof の iat / nbf60 秒より先の未来なら拒否されるようになった(全プロファイル)🔴 破壊的private_key_jwt / client_secret_jwt / JAR を使う全クライアント
3Request Object への request / request_uri 混入検知がキーベースになった🟡 挙動変更空文字・非文字列で当該キーを積んでいるクライアント
3JWT の登録済みクレームが null / 非文字列でも 500 にならず 400 / 401 を返すようになった🟡 挙動変更全テナント(改善方向・未認証到達可の穴を塞ぐ)
4require_pushed_authorization_requests: true全プロファイルで実際に強制されるようになった(従来は discovery に広告されるだけの dead flag)🔴 破壊的当該フラグを true にしているテナント
5外部ソース(federation / 外部API / 外部パスワード / 外部トークン)で解決した1要素目のユーザーが既存ユーザーに重ねる形になり、そのセッションのトークンに載るクレームが増える🟡 挙動変更外部ソースで1要素目を解決する全構成
5user_resolvecustom_propertiesキー単位マージになった(従来は全置換)🟡 挙動変更複数の認証方式が custom_properties を書く構成
5認証 executor で $.request_attributes.*解決するようになった(従来は常に null)🟡 挙動変更external-api-authentication のマッピング
5user_resolve のマッピングから $.user を参照できるようになり、1要素目は user_mapping_rules2 回評価される🟡 挙動変更user_mapping_rulesnow / uuid4 / random_string を書いている構成
6authorization-server の GET が jwks署名鍵の秘密部)を返すようになった。監査ログの before/after にも入る🟡 挙動変更 / 🔒 要点検管理APIの利用者・監査ログの保管
6federation 設定の更新が全置換セマンティクスになった🔴 破壊的federation 設定を部分更新している自動化
6テナント更新で name反映されるようになった(従来は黙って無視)🟡 挙動変更テナント更新APIの利用者
6federation 設定の一覧が disabled も返すようになった🟡 挙動変更一覧を件数・全件有効前提で扱っている自動化
6管理APIのレスポンスにフィールドが増えた(authentication-policy の policies / grant の grant_type 他 / federation の sso_provider🟡 挙動変更レスポンスを厳密なスキーマで検証しているクライアント
7ui_locales / acr_values要求順を保持するようになった(DB に保存される値の順序も変わる)🟡 挙動変更保存値の順序に依存している構成
8FAPI 2.0 Security Profile Final プロファイルと DPoP (RFC 9449) を追加。DBマイグレーション 2 本が必要🟢 追加 / ⚙️全環境(マイグレーション必須)
8/userinfo が 401 時に WWW-Authenticate チャレンジを返すようになった🟡 挙動変更401 応答をヘッダー無し前提で扱っているクライアント
9認可画面の既定が同梱静的ページから app-view に、admin テナントの cookie_same_siteNoneLax に(テンプレート)🔒 要点検テンプレートからセットアップ済みの既存テナント
10同意画面で配列クレームの要素を選べるようになった(view-data に claim_values/authorizegranted_claim_values🟢 追加claims:* スコープで配列の custom_properties を解放している構成
10view-data のレスポンスに claim_values が増えた🟡 挙動変更view-data を厳密なスキーマで検証しているクライアント

種別: 🔴 破壊的 / 🟡 挙動変更 / 🟢 追加 / ⚙️ 内部・運用 / 🔒 要点検(更新では直らず、設定の見直しが必要)


1. http_requests で response_resolve_configs のステータスが丸められなくなった(Issue #1783 / PR #1796)

response_resolve_configs429 / 503 などにマップしても、execution.function: http_requests(複数リクエスト)の interaction では 400 か 500 に丸められていた。単発の http_request は元のコードを保持していたため、同じ設定でも executor によって結果が変わっていた。

1.1 Before / After

上流の応答設定した mapped_status_codeBeforeAfter
500(レート制限を示す業務コード)429400429
503(サービス時間外)(マップなし・透過を期待)500503

とくに 429 が 400 に落ちるため、「利用者の入力誤り」と「レート制限・混雑」がクライアントから区別できなかった。429 にマップしないほうがまだ安全(500 のままなら障害系として扱える)という、設定意図と逆の判断を強いられていた。

1.2 挙動変更(注意)

mapped_status_code を 4xx / 5xx に設定している構成: クライアントが受け取る status が変わる。たとえば 429 を設定していた場合、これまで 400 だったものが 429 になる。ステータスコードで分岐しているクライアントは追従が必要。

4xx / 5xx の別は変わらないため、isClientError() / isServerError() 相当の粒度でハンドリングしているクライアントには影響しない。

透過するコードは以下のとおり。

mapped_status_codeinteraction が返す status
401 / 403 / 404 / 408 / 409 / 429 / 500 / 502 / 503 / 504そのまま
上記以外の 4xx(410 / 422 / 451 等)400
上記以外の 5xx(501 / 507 等)500
400 未満(200 / 202 / 204 等)200(成功扱い)

丸め規則(列挙外の 4xx → 400 / 5xx → 500、400 未満 → 200)は変更前後で共通。本変更は http_requests でも列挙されたコードが透過するようになったもの(http_request は従来から透過)。

1.3 フェデレーション側(Issue #1800 / PR #1802)

同じ丸めがフェデレーションの callback にもあり、しかも2段あった。

上流の応答(429)
→ UserinfoExecutionStatus 3値 … ここで 400 に丸め
→ FederationInteractionStatus 3値 … ここでも 400 に丸め
→ callback のレスポンス

両方に 401 / 403 / 404 / 408 / 409 / 429 / 500 / 502 / 503 / 504 を追加した。

対象BeforeAfter
上流 userinfo が 429 を返した400429
上流 userinfo が 503 を返した500503
response_resolve_configs503 にマップ500503
token リクエストの失敗400 / 500上流の実ステータス
JWKS 取得の失敗400 / 500上流の実ステータス
ID Token 検証の失敗400400(変更なし)

FederationInteractionStatus は userinfo 専用ではないため、token リクエストと JWKS 取得の失敗も上流の実ステータスで返るようになる。

ID Token 検証の失敗は対象外です。上流のHTTPステータスという概念がなく、OidcFederationInteractor400 を直接渡しているため、変更前後で同じです。

standard(Google / Azure AD 等)・facebookoauth-extension の全プロバイダー型が対象。とくに standard / facebookresponse_resolve_configs を使えないため、上流の実ステータスだけが情報源であり影響が大きい。

レスポンスヘッダーの形を統一

standard / facebook$.http_request.response_headers.* に HTTPヘッダーを配列のまま入れていた(["application/json"])。oauth-extension は先頭の値だけを取り出しており、同じ書き方でもプロバイダー型によって形が違った。

oauth-extension 側に揃えて文字列にした。

BeforeAfter
standard / facebook["application/json"]"application/json"
oauth-extension"application/json"変更なし

userinfo_mapping_rules でヘッダーをクレームに写している場合、値が配列から文字列に変わる。配列前提の変換関数を挟んでいれば見直しが必要。同名ヘッダーが複数ある場合は先頭の値のみになる。

ステータスコードのキー名を修正(🔴 破壊的)

standard / facebook のステータスコードのキーは staus_code(タイポ)でした。status_code に修正し、旧キーは削除しています。

BeforeAfter
standard / facebook$.http_request.staus_code$.http_request.status_code
参照している設定は無言で壊れます

staus_code を参照している userinfo_mapping_rules があれば、status_code に書き換えてください。参照パスが解決しなくなってもエラーにはならず、値が null になるだけです(#1646)。設定の登録も GET も成功するため、実行時まで気づけません。

移行の確認は1回で済みます

ヘッダーの形とキー名の変更は、どちらも standard / facebook$.http_request.* を参照する userinfo_mapping_rules が対象です。同じ箇所を見ればよいため、確認は一度で済みます。

なお、どちらもリポジトリ内に参照する設定・テスト・ドキュメントが存在しないことを確認しています(ステータスコードを参照する例はドキュメントに一度も現れていませんでした)。

4xx / 5xx の別は変わりません

isClientError() / isServerError() 相当の粒度でハンドリングしているクライアントには影響しません。

1.4 移行手順

設定変更・データ移行は不要。response_resolve_configshttp_requests で使っている場合のみ、クライアント側のステータスコード分岐を確認する。

これまで丸められた 400 / 500 を前提にしていた場合は、意図した mapped_status_code が返るようになるため、設定を書いたときの意図どおりに動くようになる方向の変更。

1.5 動作確認

ケーステスト
http_requests でも丸められず 429 が返るe2e/.../integration/authentication-interactors/integration-02-authentication-interactor-response-resolve-configs.test.js
単発 http_request の透過(従来どおり)同上
フェデレーション callback が上流の 429 を返すe2e/.../usecase/advance/advance-18-federation-userinfo-status-code.test.js
フェデレーション callback が response_resolve_configs503 を返す同上
standard / facebook プロバイダーが上流ステータスを保持するOidcExecutorUserinfoStatusTest(federation-oidc unit)
両 enum の分類・fromStatusCodeUserinfoExecutionStatusTest / FederationInteractionStatusTest

1.6 関連

  • HTTP Request Executor — Response Resolver 節
  • HttpRequestsAuthenticationExecutor#createErrorResult / AuthenticationExecutionStatuslibs/idp-server-core/.../authentication/interaction/execution/
  • UserinfoExecutionStatus / FederationInteractionStatus / StandardOidcExecutor / FacebookOidcExecutor(フェデレーション側)
  • v0.12.0 の 2 番(response_resolve_configs の配列統一・authentication 側での実行時適用)の続き。v0.12.0 影響確認

2. external-api-authentication の認証結果に interaction ごとの内訳を追加(Issue #1771 / PR #1804)

external-api-authentication は1つの設定に複数の interaction を持てますが、認証結果はすべて external-api-authentication という単一のキーに集約されていました。そのため authentication-policy から「どの interaction が成功したか」を区別できませんでした。

2.1 何が書けなかったか

// 3つの interaction を通したいが、合計しか参照できない
{ "path": "$.external-api-authentication.success_count", "operation": "gte", "value": 3 }

これは1つの interaction を3回呼んでも成立するため、必須の interaction を通さないまま認証を完了できました。failure_count / lock_conditions も同様で、interaction ごとに試行回数の上限を変えられませんでした。

2.2 変更内容

結果に interactions を追加しました。合計は従来どおり全 interaction の和です。

"external-api-authentication": {
"success_count": 3,
"interactions": {
"step-a": { "success_count": 1, "failure_count": 0, "call_count": 1 },
"step-b": { "success_count": 2, "failure_count": 0, "call_count": 2 }
}
}

ポリシーからは $.external-api-authentication.interactions.step-a.success_count で参照します。

2.3 出荷テンプレートに認証バイパスがありました(🔒 要点検)

内訳を追加する過程で、ciba-delegation の出荷テンプレートがこの問題を実際に踏んでいることが判明しました。

ciba-delegation は3つの interaction(ciba_startciba_polluserinfo)で構成されますが、2要素目の条件は合計で書かれていました。

{ "path": "$.external-api-authentication.success_count", "operation": "gte", "value": 3 }

ciba_start は CIBA リクエストを開始するだけで端末の承認を待たず、呼ぶたびに成功します。そのため次の経路が成立しました。

password(1要素目で本人確定)
→ ciba_start ×3
→ success_count が 3 に到達 → 認証完了 → 認可コード発行

端末での承認・ciba_polluserinfo を一度も通さずに2要素目が成立します。

バージョンを上げても直りません

テンプレートはセットアップ時に手動で流し込むもので、起動時に再適用される仕組みはありません。認証ポリシーはテナントごとにデータベースへ保存されているため、本バージョンへ更新してもテンプレートの修正は既存テナントに反映されません

つまりこの変更で壊れるものは何もありませんが、すでに構築済みのテナントは脆弱な設定のまま残ります。管理APIでポリシーを更新してください。

config/templates/use-cases/ciba-delegation/authentication-policy-oauth.json をベースにテナントを構築している場合、success_conditions を内訳参照に更新してください。

"success_conditions": {
"any_of": [[
{ "path": "$.password-authentication.success_count",
"type": "integer", "operation": "gte", "value": 1 },
{ "path": "$.external-api-authentication.interactions.ciba_start.success_count",
"type": "integer", "operation": "gte", "value": 1 },
{ "path": "$.external-api-authentication.interactions.ciba_poll.success_count",
"type": "integer", "operation": "gte", "value": 1 },
{ "path": "$.external-api-authentication.interactions.userinfo.success_count",
"type": "integer", "operation": "gte", "value": 1 }
]]
}

適用は全インスタンスの更新後に

この修正ポリシーは v0.13.0 以降でのみ機能します

内訳(interactions.*)は本バージョンで追加されたものです。ローリングデプロイ中(新旧混在)に修正ポリシーを適用すると、そのテナントの認証がすべて完了しなくなります。

旧インスタンスは内訳を復元しないため参照パスが解決せず、条件が満たされないまま in_progress で止まります(詳細は 2.5)。

セキュリティ修正は急いで適用したくなりますが、全インスタンスの更新完了を確認してからポリシーを更新してください。

暫定回避策はありません

旧バージョンのままでは合計しか参照できず、条件式でこの問題を回避する手段はありません

利用できる演算子(eq / gte / in など)はいずれも単一のパスの値に対する比較で、合計は1つの数値です。「3種類の interaction を通った」と「1つを3回呼んだ」を合計だけで区別できません。閾値を上げても、その回数だけ ciba_start を呼べば満たされます。

運用上の緩和は、該当ポリシーを無効化する(enabled: false)ことのみです。ただしこれはその認証方式自体を停止することになります。

同様の設定をしていないか点検してください。 external-api-authentication の合計 success_count で条件を書いている構成は、同じ interaction を繰り返すだけで成立します。とくに「複数の interaction を順に通す」ことを意図している場合は該当します。

なお pending を返す ciba_poll は 400(失敗)のため合計には入りません。正常系の 3 は「成功した3段」と一致しており、そこは意図どおりでした。問題は同じ interaction を繰り返せる点にあります。

2.4 影響(機能追加そのもの)

内訳の追加自体は 🟢 で、既存設定の挙動は変わりません。

対象影響
既存の success_conditions(合計を参照)なし。合計は従来どおりの和
external-api-authentication 以外の認証方式なし。interactions キー自体が出力されません
保存済みの認証トランザクションなし。interactions の無い行は内訳が空として読み込まれます
/v1/authorizations/{id}/authentication-status のレスポンスinteraction_results 配下に interactions キーが増えます

最後の1点のみ、レスポンスを厳密なスキーマで検証しているクライアントは追従が必要です(キーの追加のみ)。

2.5 移行手順とデプロイ順序

設定変更は不要です。多段フローで各 interaction を必須にしたい場合のみ、success_conditions を内訳参照に書き換えてください。

内訳を参照するポリシーは、全インスタンスの更新後に設定してください

ローリングデプロイ中(新旧インスタンスが混在している状態)で内訳参照のポリシーを設定すると、認証が完了しなくなります

旧バージョンのインスタンスは認証結果から内訳を復元しません。そのため、

  1. 内訳参照のパス($.external-api-authentication.interactions.step-a.success_count)が解決せず null になる
  2. gte などの数値比較は null に対して false になる(#1646)
  3. 条件が満たされず、認証ステータスが in_progress のまま進まない

さらに旧インスタンスが認証トランザクションを保存し直すと、それまでに記録された内訳が失われます(合計は保持されます)。認証フローが新旧インスタンスをまたぐと、すべての interaction を実行しても条件が揃いません。

エラーにはならず in_progress のまま止まるため、利用者からは原因が分かりません。

推奨手順:

  1. 全インスタンスを更新する
  2. 更新完了後に、内訳参照のポリシーを設定する

内訳を使わない構成(合計のみを参照、または external-api-authentication を使わない)は、混在中も影響を受けません。

認証を通してしまう方向には倒れません

条件が満たされない側(in_progress)に倒れるため、必須の interaction を実行しないまま認証が完了することはありません。混在中の影響は「完了できない」であり、「通ってしまう」ではありません。

2.6 保存済みデータの互換性

状況挙動
旧バージョンで開始した認証を新バージョンが読む内訳が無いものとして読み込み、以降のステップから内訳を記録
新バージョンで記録した内訳を旧バージョンが読む内訳を無視して読み込む(エラーにはならない)。保存し直すと内訳は失われる
完了済み・保存済みの認証トランザクション影響なし

データ移行・マイグレーションは不要です(interaction_results は JSON カラムで、キーの追加のみ)。

2.7 動作確認

ケーステスト
1つの interaction を3回呼んでも3段の条件を満たさないe2e/.../usecase/advance/advance-19-external-api-interaction-results.test.js
各 interaction を1回ずつ呼ぶと認証が完了する同上
内訳の集計・ポリシーパス・保存往復・後方互換AuthenticationInteractionBreakdownTest(core unit)
ciba_start を3回呼んでも2要素目が成立しないe2e/.../scenario/application/scenario-13-oauth-ciba-delegation.test.js

2.8 関連


3. JWT 検証の厳格化(Issue #1776 / #1779 / PR #1526 / #1777 / #1780)

3.1 iat / nbf に 60 秒の上限がついた(🔴 破壊的)

FAPI 2.0 対応(PR #1526)で JwtClockSkewValidator を新設し、プロファイルを問わず次の3経路から呼ぶようにしました。

経路呼び出し元
client assertion(private_key_jwt / client_secret_jwtClientAuthenticationJwtValidatable
DPoP proofDPoPProofVerifier
Request Object(JAR)RequestObjectVerifyable

iat または nbf が idp-server の時計より 60 秒より先の未来なら拒否します(FAPI 2.0 Security Profile §5.3.2.1-2.13)。

経路拒否時
client assertionClientUnAuthorizedExceptioninvalid_client(401)
Request ObjectRequestObjectInvalidExceptioninvalid_request_object
DPoP proofDPoPProofInvalidException(400)

これまで上限は無く、未来の iat はいくら先でも素通りしていました。

クライアント側の時計が 60 秒以上進んでいると、これまで通っていたリクエストが通らなくなります

FAPI 2.0 テナントに限らず、private_key_jwt / client_secret_jwt / JAR を使う全クライアントが対象です。NTP 同期されていれば影響しません。

同期していないホストからクライアント認証している場合は、アップグレード前に時刻同期を確認してください。エラーは invalid_client として返るため、クライアント側からは「認証情報が誤っている」ようにしか見えません。

過去方向(iat が古い)に制限は入れていません。exp の検証は従来どおりです。

FAPI 条項との関係

60 秒の上限は FAPI 2.0 由来で、FAPI 1.0 の条項とは縛っている軸が違います。

仕様条項内容方向対象idp-server の実装
FAPI 2.0 SP Final§5.3.2.1-13iat / nbf が未来 0〜10 秒なら受理し、60 秒超は拒否未来JWT 全般JwtClockSkewValidator(v0.13.0 新規・全プロファイル
FAPI 1.0 Advanced§5.2.2-13request object の expnbf から 60 以内有効期間request object のみFapiAdvanceVerifier(v0.13.0 以前から・FAPI Advance 時のみ)
FAPI 1.0 Advanced§5.2.2-17request object の nbf は過去 60 以内過去request object のみ同上

FAPI 1.0 Advanced の 2 条項は「request object の古さ・有効期間」を、FAPI 2.0 の条項は「未来方向のずれ」を縛るもので、重複も矛盾もしません。FAPI Advance プロファイルのテナントは、v0.13.0 で既存の 2 条項に加えて未来 60 秒の上限が乗ります。

条文(FAPI 2.0 §5.3.2.1-13)は次のとおりで、10〜60 秒の扱いは実装裁量です。

to accommodate clock offsets, shall accept JWTs with an iat or nbf timestamp between 0 and 10 seconds in the future but shall reject JWTs with an iat or nbf timestamp greater than 60 seconds in the future

idp-server は iat > now + 60秒 を拒否する実装で、ちょうど 60 秒は受理します(裁量の範囲で最も緩い側)。

全プロファイルに掛けたのは意図的です

60 秒も先の iat を打つクライアントは、厳しく弾かれているのではなく単に時計が壊れています。 正常に運用されているクライアントがこの上限に触れることはないため、FAPI 2.0 の条項ではあるものの、プロファイルを問わない既定として採用しました。リプレイの窓を絞る効果もあります(JwtClockSkewValidator の Javadoc に同趣旨を記載)。

したがってこれは仕様適合のための措置ではなく、idp-server の判断で適用範囲を広げたものです。利用者影響の本体はここにあります — FAPI を使っていないテナントも対象になるため、時刻同期されていないホストからクライアント認証している構成だけが影響を受けます。

3.2 登録済みクレームが null / 非文字列でも 500 にならなくなった(🟡)

JsonWebTokenClaims.hasXxx() がキーの存在(containsKey)だけを見ていたため、値が JSON null のクレームでガードを通過し、直後の getXxx() が null を返して NPE → HTTP 500 になっていました(#1776)。getValue() も値を無条件に (String) へキャストしていたため、数値・配列でも同じ形で落ちました(#1779)。

DPoP proof は proof 自身に埋め込まれた公開鍵で署名検証されるため、攻撃者が自己署名した jti:null / htm:123 の proof で、未認証のまま 500 を発生させられる状態でした。

BeforeAfter
{"jti": null} の DPoP proofNPE → 500DPoPProofInvalidException400
{"htm": 123} の DPoP proofClassCastException → 500400
null / 非文字列クレームを含む client assertion・Request Object・外部IdP の id_token500400 / 401

正常な JWT は登録済みクレームに null を持たないため、正規のトラフィックには影響しません。

3.3 Request Object の request / request_uri 混入検知がキーベースになった(🟡)

JAR (RFC 9101) §4 は "request and request_uri parameters MUST NOT be included in Request Objects" と定めており、含まれていること自体が違反です。従来は値を取り出して「非 null かつ非空」で判定していたため、次が素通りしていました。

Request Object の中身BeforeAfter
{"request": "..."}(文字列)拒否拒否(変更なし)
{"request": ""}素通り拒否
{"request": 123}ClassCastException → 500拒否(invalid_request_object

エラーメッセージの章番号も JAR Section 6.2JAR Section 4 に訂正しています(§6.2 は別内容)。

変わったのは混入検知だけで、パラメータのマージ規則は従来どおりです

JAR は「認可サーバーは request object ののパラメータだけを使う」(§5 / §6.3)と定めており、これは OIDC Core §6.3.3 の「両側にあれば中を優先してマージする」からの破壊的変更です。v0.13.0 はこの合成規則には手を付けていません。

idp-server の合成規則はプロファイルで分岐します(OAuthRequestContextCreator#selectRequestObjectType)。

プロファイル実装規則
FAPI_ADVANCEFapiAdvanceRequestObjectPatternFactoryJAR 準拠。request object の中だけを使う
それ以外(OAUTH2 / OIDC / FAPI_2_0 / FAPI Baseline)RequestObjectPatternFactoryOIDC Core §6.3.3 準拠。中にあれば優先し、無ければ外側のクエリパラメータにフォールバック

分岐は profile.isFapiAdvance() のみを見ているため、FAPI_2_0 は OIDC Core 側の合成規則になります。FAPI 2.0 Security Profile Final の必須要件は PAR であり、本リポジトリの documentation/requirements/fapi-2.0-requirements.yaml にも request object / JAR の条項はありません。JAR を必須とするのは FAPI 2.0 Message Signing の領分のため、現状の選択が適合上の問題になるかは Message Signing に着手する際に判断します。

3.4 動作確認

ケーステスト
Nimbus の null クレーム挙動のピン留め・hasXxx() の契約JsonWebTokenClaimsTest / NimbusJoseBehaviorTest(platform)
60 秒超の iat / nbf を拒否・null で NPE を出さないJwtClockSkewValidatorTest(platform)
自己署名 proof(jti:null)が 400 になるDPoPProofNullClaimTest(core)
JAR §4 の混入拒否e2e/src/tests/spec/rfc9101_jar.test.js

4. require_pushed_authorization_requests が実際に強制されるようになった(🔴 破壊的)

Issue #1736 クラスタD / PR #1741。

AuthorizationServerConfiguration.requirePushedAuthorizationRequests(RFC 9126 §5)は discovery に広告されるだけで、どの verifier からも参照されない dead flag でした。実際の PAR 強制は fapi20_scopes 由来の FAPI 2.0 プロファイル限定で、非 FAPI テナントがフラグを立ててもメタデータどおりには動きませんでした。

RequirePushedAuthorizationRequestVerifier を新設し、OAuthRequestVerifier の extension verifier 列に登録しました。

BeforeAfter
require_pushed_authorization_requests: true のテナントへの直接認可リクエストそのまま処理invalid_request(redirect でエラー返却)
PAR エンドポイント自身へのリクエスト処理処理(変更なし。isAtPushedEndpoint() でスキップ)
false(既定)のテナント処理処理(変更なし)
フラグを立てているテナントは、PAR を経由しないクライアントが弾かれます

既定値が false のため大半のテナントは無影響ですが、過去に「広告だけ」のつもりで true にしたテナントは、この変更で直接認可リクエストが拒否されるようになります。設定を変えていないのに挙動が変わる唯一のケースです。

アップグレード前に該当テナントを確認してください。

-- PostgreSQL
SELECT tenant_id
FROM authorization_server_configuration
WHERE payload::jsonb ->> 'require_pushed_authorization_requests' = 'true';
-- MySQL
SELECT tenant_id
FROM authorization_server_configuration
WHERE JSON_EXTRACT(payload, '$.require_pushed_authorization_requests') = TRUE;

該当した場合の選択肢は 2 つです。

  1. 意図どおり(PAR を必須にしたかった)→ クライアントを PAR 経由に移行してからアップグレードする
  2. 意図と違う(広告だけのつもりだった)→ アップグレード前にフラグを false に戻す

なお、6.1 の修正が入るまでは、管理APIの GET → PUT 往復でこのフラグが truefalse に戻っていました。往復で黙って解除されていたテナントは、v0.13.0 で往復しても解除されなくなります。この2つは組み合わせて効きます。

ケーステスト
discovery が true を広告 / 直接リクエストが invalid_request / PAR push は受理e2e/src/tests/spec/rfc9126_par_required.test.js(専用テナント require-par-tenant
shouldVerify マトリクスと拒否・許可RequirePushedAuthorizationRequestVerifierTest(core unit)

5. 外部ソースで解決したユーザーの扱いが変わった(Issue #1767 / #1772 / #1773 / #1786 / #1792 / PR #1784 / #1785 / #1788 / #1793 / #1794 / #1795)

user_resolve とマッピング周辺の 6 本の修正です。いずれも設定を変えずに挙動が変わります。まとめて記載します。

5.1 解決したユーザーが既存ユーザーベースになった(🟡)

外部ソースで1要素目のユーザーを解決すると、認証トランザクションのユーザーがマッピングの出力 + sub / status だけになっていました。認可グラントはそれをスナップショットするため、既存ユーザーが持っていた属性はそのセッションのトークンに載りませんでした。

出力Before
ID Token / アクセストークン❌ マッピングが出した属性しか載らない
UserInfo✅ 正しい(UserinfoHandler が DB から取り直すため)
DB のユーザーレコード✅ 正しい

とくに verified_claimsUser#updateWith の patch 対象外なので、マッピングから組み立てたユーザーには構造上絶対に載りません。身元確認済みのユーザーがフェデレーションでログインすると、そのセッションのトークンから verified_claims が落ちていました。UserInfo を見ると正しいため、気づきにくい失敗でした。

4 経路すべてを ResolvedUserCreatorexistingUser.enrichWith(mapped) + status 固定)に集約しました。

経路実装
フェデレーションOidcFederationInteractor#resolveUser
外部API認証ExternalApiAuthenticationInteractor#resolveUser
外部パスワード認証PasswordAuthenticationInteractor#resolveUserFromExternalAuth
外部トークン認証ExternalTokenAuthenticationInteractor

そのセッションのトークンに載るクレームが増えます。 既存ユーザーが持っていて外部ソースが返さない属性(verified_claims / roles / permissions / authentication_devices 等)が ID Token・アクセストークンに現れるようになります。トークンのサイズが増えるほか、クレーム集合を固定前提で扱っているクライアントは確認が必要です。

status は既存値で固定します(外部ソースが LOCKEDREGISTERED に復活させられないため)。sub / provider_id / external_user_idupdateWith の immutable 扱いで安定します。

既知のトレードオフ: 巻き戻りの窓

1要素目で取った既存ユーザーのスナップショットが authorize 時の registerOrUpdate まで持ち回られるため、認証中に管理APIでそのユーザーを PATCH すると巻き戻る窓ができます。窓は認証フローの所要時間ぶんで、次の管理操作で自己修復します。

修正前も「マッピングが出すフィールド」については同じ挙動でしたが、修正後は updateWith が patch から取る全フィールド(新たに roles / authentication_devices / assigned_tenants)が対象になります。とくに roles は権限状態なので、単なる属性の巻き戻りより意味が重くなります。

5.2 custom_properties がキー単位マージになった(🟡)

User#updateWith は「patch 側に1つでもキーがあれば map ごと差し替える」実装で、custom_properties は federation / 外部API認証 / 2要素目 / 身元確認 が書き込む共有のフラットなキー集合です。そのため後から通った方式が他方式のキーを黙って消していました。

User#enrichWith を新設し、認証フローの経路をそちらへ寄せました。

BeforeAfter
ルールが値を生成したキー上書き上書き
ルールが値を生成しなかったキーnull で上書き(PR #1788 で修正)既存値を保持
ルールが宣言していないキーmap ごと消失保持

User#updateWith は変更していません。管理API の PATCH は従来どおり全置換です(「認証フローは育てる / 管理APIは指示どおり」で意味を分けています)。

他方式が入れたキーが消えなくなるため、custom_properties を読む側が見るキー集合が増えます。「消える」ことを前提にしていた構成はありえないため、実質は改善方向の変更です。

5.3 $.request_attributes.* が解決するようになった(🟡)

認証の HTTP executor 2 つが RequestAttributestoMap() せずにマッピングコンテキストへ入れていたため、$.request_attributes.ip_address / .user_agent常に null に解決されていました。身元確認・イベント系(15箇所以上)は toMap() 済みで、この2箇所だけが逸脱していました。

箇所BeforeAfter
HttpRequestAuthenticationExecutor生で put(解決せず null).toMap()
HttpRequestsAuthenticationExecutor生で put(解決せず null).toMap()

これまで null だったパスが値を返すようになります。 external-api-authenticationbody_mapping_rules / header_mapping_rules$.request_attributes.* を書いている構成は、外部サービスへ送るリクエストの中身が変わります。「書いたが効いていなかった」ものが効き始めるため、外部サービス側が想定していない値を受け取る可能性があります。

公開範囲は身元確認とパリティで、headers を含む全キーです。headers には Authorization / Cookie が含まれるため、外部サービスへ横流ししないよう設定を確認してください。

あわせて RequestAttributes.toMap() を null 安全化しました(jsonNodeWrapper が null なら空 Map。挙動変化は「NPE → 空 Map」だけ)。

5.4 user_resolve のマッピングから $.user を参照できるようになった(🟡)

user_resolveuser_mapping_rules$.user を書いても、例外にもログにもならず黙って nullになっていました。body_mapping_rules では #1439 で使えるため、同じ記法を書いて「動いているように見えて効いていない」状態になりやすい形でした。

1要素目は、ユーザーを探すキー(provider_id / external_user_id自体がマッピングの産物であるため、二段マッピングにしています。

① user_mapping_rules を評価 → 検索キーを得る
② 既存ユーザーを検索し、mappingSource に "user" を注入
③ user_mapping_rules を再評価 → こちらが採用される

1要素目では user_mapping_rules が 2 回評価されます。 マッピング関数はすべて副作用のない値変換ですが、now / uuid4 / random_string は①と③で異なる値を返します。採用されるのは③の結果で、新規ユーザー分岐では検索キーを①の値にピン留めしています(PR #1795)。これらの関数を user_mapping_rules に書いている構成は、生成される値が「検索に使った値」と一致しない点に注意してください。

http_requests executor にも $.user を注入し、単数 http_request との非対称(同じマッピングルールが executor の選択によって効いたり効かなかったりする)を解消しました。

2要素目の書き込み側 allowlist(SECOND_FACTOR_ENRICHABLE_CLAIMS、CWE-287 / #1497)は不変で、$.user は読み取りにしか使えません。verified_claims / hashed_password / credentials は投影から除外されています。

5.5 解決しない JSONPath 引数が warn に出るようになった(⚙️)

関数の args に書いた $. 参照が解決しないと、関数はそれを吸収して動き続けていました。とくに mergesource が null だと入力をそのまま返すため、設定上は「既存値に積み増す」と読めるルールが実際には「今回の値で置き換える」になっていました。

未解決時に function / arg / path / to を warn へ出します。ルール自身の from は対象外です(パス不在は通常の分岐で、意図的に解決させないイディオムが使われているため)。

ログ量が増えます。既存設定に未解決の args があれば、アップグレード後に warn が出始めます。

5.6 動作確認

ケーステスト
外部ソースが返さない属性がトークンに載る(4経路)e2e/.../usecase/advance/advance-17-external-1st-factor-token-claims.test.js / advance-16-external-token-response-mapping.test.js
status 固定・識別子の不変・custom_properties のキー単位マージResolvedUserCreatorTest(core unit・6ケース)
方式をまたいだ custom_properties のマージe2e/.../scenario/application/scenario-14-custom-properties-merge.test.js
生成されなかったキーで既存値が消えないUserEnrichWithTest(core unit)
$.request_attributes.* の解決e2e/.../integration/authentication-interactors/...-request-attributes.test.js
user_resolve から $.user を参照e2e/.../scenario/application/scenario-15-user-resolve-existing-user.test.js
未解決 args の warnMappingRuleObjectMapperArgsResolutionTest

5.7 関連


6. 管理APIの GET が返す表現と更新セマンティクス(Issue #1742 / #1743 / #1745 / #1755 / #1762 / #1729 / PR #1746 / #1748 / #1749 / #1750 / #1764 / #1765)

管理APIの update は全置換です。したがって次の不変条件が要ります。

GET が返す表現は、update が読み戻せる全フィールドを含まなければならない。

v0.13.0 ではこれが守られていなかった箇所を横断で塞ぎました。GET のレスポンス構造が変わるため、管理APIを自動化している利用者は影響を受けます。

6.1 authorization-server の GET が全フィールドを返すようになった(🔒 要点検)

GET → 編集 → PUT の read-modify-write で、GET が出力していなかったフィールドが黙って失われていました。差分も同じ toMap() 同士の比較で計算するため、dry_run の diff にも現れませんでした。

実測(PR #1764)修正前
GET レスポンスの jwksキーごと欠落
GET 結果を PUT した dry_run の diff{}
往復後の /{tenant}/v1/jwks500

AuthorizationServerConfiguration#toMap()jwks / require_pushed_authorization_requests / revocation_endpoint(+ auth_methods, auth_signing_alg)/ introspection_endpoint(同)/ authorization_encryption_alg_values_supported / authorization_encryption_enc_values_supported / authorization_details_types_supported を追加しました。

管理APIのレスポンスと監査ログに署名鍵の秘密部が入ります

jwks秘密鍵を含む設定値です。修正により次の 2 箇所に現れるようになります。

出力先経路
authorization-server 管理APIの GET / PUT レスポンスAuthorizationServerFindService / AuthorizationServerUpdateService
監査ログの before_payload / after_payloadAuthorizationServerManagementContext#beforePayload() / afterPayload()

discovery(/.well-known/openid-configuration)と JWKS エンドポイントには影響しません。 discovery は toMap() を使わず独自にマップを組んでおり、JWKS エンドポイントは JwkParser.parsePublicKeys() で公開鍵だけを返します。

監査ログには期限切れ削除の仕組みがありません。 oauth_token / authorization_request / authorization_code_grant / sso_session / authentication_transaction には削除の実装がありますが、audit_log を削除する経路はコード上に存在しません。一度書かれた秘密鍵はテーブルに残り続けます。

次を点検してください。

  • authorization-server の管理API権限を持つ operator の範囲
  • 監査ログの保管先・アクセス権・ログ転送先(外部 SIEM 等へ流している場合は転送先にも秘密鍵が渡ります)

credential_issuer_metadata は対象外です(VC のモデルが暫定で、保存されているメタデータの全メンバーを保持していないため。出力すると「モデルが知らない部分を落とす往復」を成立したように見せてしまいます)。

6.2 federation 設定の更新が全置換になった(🔴 破壊的)

federation 設定は、経路ごとに enabled の扱いが食い違っていました。

経路Before原因
updateenabled=false を送っても true に戻る4引数コンストラクタで再構築
read (GET)常に trueSELECT が enabled 列を取っていない
createenabled=false を無視手動抽出 + 4引数コンストラクタ
listdisabled が常に消えるselectListAND enabled = true をハードコード

ClientConfiguration / ClientUpdateService に揃えて、update / create を jsonConverter.read(map, ...) による全置換にしました。

部分更新していると、送らなかったフィールドが消えます

これまで update は request の一部フィールドしか見ておらず、結果として「送らなかったフィールドは残る」ように見えていました。全置換になったことで、ボディに含めなかったフィールドは消えます

federation 設定を PUT する自動化は、GET の結果をベースに全フィールドを送る形に直してください。sso_provider だけは例外で、リクエストに非空の値が無ければ既存値を維持します(6.3)。

一覧(list)は 既定で全件を返し、enabled はクエリフィルタになりました(?enabled=true で絞れます)。管理者がデータを確認できるようにするためで、client と同型です。

一覧のレスポンス件数が増えます。 disabled の設定を持つテナントで、一覧を件数前提・全件有効前提で扱っている自動化は追従が必要です。

ランタイムの federation フローが使う selectOneincludeDisabled は不変です(「enabled のみ取得」に依存しているため)。

6.3 federation の sso_provider が GET で返るようになった(🟡)

sso_provider設定行の引き当てキーです(専用カラム sso_provider VARCHAR(255) NOT NULLUNIQUE(tenant_id, type, sso_provider))。federation リクエスト時に WHERE sso_provider = ? で設定行を fetch します。

BeforeAfter
GET レスポンスの sso_provider返らない返る
部分更新で sso_provider を省略空文字に潰れて永続化され、以降 config not found で federation が破綻既存値を維持

update SQL は毎回 sso_provider = ? を書くため、空文字が NOT NULL 制約を通過して保存され、外部IdP連携が動かなくなっていました。

6.4 テナント更新で name が反映されるようになった(🟡)

TenantUpdateService.updateTenant()after を組む際、request の name ではなく **before.name()(既存 name)**を渡していました。before/after が一致するため diff も空でした。

これまで無視されていた name が反映されます。 テナント更新API に「変えるつもりのない name」をボディへ含めていた自動化は、その値でテナント名が上書きされます。GET の結果をそのまま PUT している場合は同じ値なので影響しません。

identifier / type / authorization_provider / main_organization_identifier は不変項目として before を維持します。

6.5 認証ポリシー一覧が policies を返すようになった(🟡)

AuthenticationPolicyConfigurationQueryDataSource#findList だけが、行 Map({id, flow, payload})をそのままデシリアライズしていたため、policies常に空配列でした。idflow は行のカラムでもあるためマップされてしまい、レスポンスが一見それらしく見えるので壊れていることが分かりにくい形でした。

実測(PR #1765・limit=3修正前修正後
policies の件数0 / 0 / 02 / 1 / 1

detail(find / get)とランタイムは元から正しく、DB のデータも正常でした。selectList を持つ QueryDataSource 19 本を横断で確認し、同型は本箇所のみでした。

一覧のレスポンスサイズが増えます。 これまで空だった policies に実体が入ります。

6.6 grant 管理APIのレスポンスにフィールドが追加された(🟢)

AuthorizationGranted.toMap() に追加しました。読み取り経路で DB から復元済みのため、追加クエリは発生しません。

フィールド出力条件
grant_type常時(OpenAPI で required)
authorization_details / id_token_claims / userinfo_claims / custom_properties存在時のみ

一覧・詳細とも AuthorizationGranted.toMap() を通るため、両方に効きます。

6.7 移行手順

設定変更・データ移行は不要です。管理APIを自動化している場合のみ、次を確認してください。

対象確認
federation 設定を PUT しているGET の結果をベースに全フィールドを送る形にする6.2
federation 設定を一覧しているdisabled が混ざる。必要なら ?enabled=true
テナントを PUT しているボディの name がそのまま反映される
レスポンスを厳密なスキーマで検証しているauthorization-server(jwks 他)/ federation(sso_provider)/ authentication-policy(policies)/ grant(grant_type 他)でキーが増える
監査ログを外部へ転送しているauthorization-server の before/after に秘密鍵が入る(6.1

6.8 動作確認

ケーステスト
GET → PUT 往復で全フィールドが保たれるAuthorizationServerConfigurationRoundTripTest / ManagementRepresentationCompletenessTest
federation の enabled・全置換・一覧フィルタorganization_federation_config_management.test.js / FederationConfigCreationServiceTest / FederationConfigUpdateServiceTest
sso_provider の保全FederationConfigurationTest / 同 E2E
テナント name の反映organization_tenant_management.test.js / ..._structured.test.js
一覧の policies の中身organization_authentication_policy_config_management.test.js
grant のフィールド出力AuthorizationGrantedTest / organization_grant_management.test.js

7. ui_locales / acr_values が要求順を保持するようになった(Issue #1801 / #1811 / PR #1809)

UiLocales / AcrValues が値を HashSet に入れていたため、空白区切りの優先順位が失われていました。どちらも仕様上は順序に意味があります(ui_locales は "ordered by preference"、acr_values は "in order of preference")。LinkedHashSet に変更しています。

BeforeAfter
ui_locales=fr-CA fr en順序不定(en fr-CA fr などになる)fr-CA fr en
acr_values=... silver ... bronze順序不定要求順

7.1 影響

acr_values の消費側の挙動は変わりません。 参照は 3 箇所とも contains() による membership 判定で、順序を見ていません。

一方 toStringValues()保存している箇所が 5 つあり(認可リクエスト / CIBA バックチャネル認証リクエスト / 認証トランザクション、いずれも両DB)、DB に残る値の順序が変わります

保存済みの行は変わりません。v0.13.0 以降に作られた行だけが要求順になります。認可リクエストや認証トランザクションの acr_values / ui_locales を読んで順序に依存した処理をしている場合は確認してください。

ui_locales は空要素も落とすようになりました(区切りの重複で空タグが混ざると、画面が解決すべき locale として扱ってしまうため)。acr_values の空要素の扱いは変えていません。

7.2 ui_locales が画面まで届くようになった(🟢)

これまで ui_localesAuthorizationRequest まで運ばれていましたが、そこから先で誰も読んでいませんでした。画面へ渡す経路も 3 つとも塞がっていました。

経路
view-data配列 ["fr-CA", "fr", "en"]
サインイン画面へのリダイレクトURL空白区切り ?ui_locales=fr-CA+fr+en

ui_locales_supported によるフィルタはしません(仕様上サポート外の locale でもエラーにはせず、どのバンドルを持つかを知っているのは画面側のため)。

7.3 discovery に ui_locales_supported が出るようになった(🟢)

ui_locales_supported は全テナントテンプレートに書かれ、OpenAPI にも記載されているのに AuthorizationServerConfiguration にフィールドが無く、Jackson が読み捨てていました(設定は登録できるのに discovery に出ない)。

discovery のレスポンスにキーが増えます。 ui_locales_supported を設定済みのテナントは、アップグレード後に広告され始めます。

claims_locales_supported は同じ形ですが、claims_locales リクエストパラメータ自体が未実装のため対象外にしています(無い機能を広告することになるため)。

7.4 動作確認

ケーステスト
順序保持・空要素の除去UiLocalesTest / AcrValuesTest(core unit)
discovery の ui_locales_supportedServerConfigurationResponseCreatorTest
サインインURL・view-data への伝播e2e/.../usecase/advance/advance-20-ui-locales.test.js

8. FAPI 2.0 Security Profile Final と DPoP の追加(Issue #1736 / PR #1526 / #1737 / #1738 / #1739 / #1741)

AuthorizationProfile.FAPI_2_0 と RFC 9449 DPoP を新設しました。プロファイル自体が新規のため、既存テナントは fapi20_scopes を設定するまで影響を受けません。 全プロファイルに効く変更は 3.14 に分けて記載しています。

8.1 追加された強制(FAPI 2.0 プロファイル時のみ)

要件実装
PAR 必須・直接認可リクエスト拒否 / response_type=code のみ / PKCE S256 必須 / HTTPS redirect_uriFapiSecurity20Verifier
public client 拒否・client_secret_* 拒否FapiSecurity20Verifier / AuthorizationCodeGrantFapi20Verifier
sender-constrained access token(mTLS / DPoP)FapiSecurity20Verifier
client assertion の aud は AS issuer の string のみAuthorizationCodeGrantFapi20Verifier
client assertion / DPoP proof の alg は PS* / ES* / EdDSA のみ(§5.4)AuthorizationCodeGrantFapi20Verifier / DPoPProofVerifier.verifyFapiSigningAlgorithm
Authorization Code Binding (dpop_jkt, RFC 9449 §10.1)AuthorizationCodeGrantService

DPoP proof の alg 制限は、テナントが dpop_signing_alg_values_supported未設定にしても RS256 等を拒否します(設定リストの上書きではなく検証で弾く形)。

refresh の §5.4 は対象外です。 AuthorizationGrant に profile が保存されておらず、refresh には profile dispatch verifier もないためです。影響は「RSA 鍵バインドの FAPI 2.0 トークンが refresh 時に RS256 へダウングレードする」edge のみで、EC 鍵なら同じ ES256 に固定されます。

8.2 全テナントに効く DPoP 周辺の変更

変更影響
/userinfo が 401 時に WWW-Authenticate チャレンジを返すようになった(RFC 9449 §7.1)🟡 Bearer を使う既存クライアントも対象。401 応答にヘッダーが増える
/me の DPoP エラーが invalid_dpop_proofinvalid_tokeninvalid_dpop_proof はトークンエンドポイント専用)⚙️ DPoP 自体が v0.13.0 の新機能のため実質影響なし
WWW-Authenticateerror_description を RFC 7235 quoted-string 用にエスケープ(制御文字除去)⚙️ ヘッダーインジェクション対策
X-Forwarded-Proto / X-Forwarded-Hostカンマ区切りの先頭値で取るようになった🟡 多段プロキシ(CloudFront/API Gateway → ALB、nginx 多段)で htu が破損して DPoP が全拒否になるのを解消
htu の信頼境界

X-Forwarded-Proto / X-Forwarded-Host受信値をそのまま使います。htu の完全性は「idp-server が信頼イングレス(reverse proxy / API Gateway + ALB)経由でのみ到達可能」というネットワーク隔離に依存します。

アプリ層の source-IP allowlist は意図的に入れていません。クラウド構成で idp-server が見る即時ピアは動的な内部アドレス(VPC Link / 内部ALB)で、allowlist で塞ぐと DPoP 検証が全面的に壊れる可用性リスクのほうが大きいためです。trusted_proxies 設定は X-Forwarded-For(クライアントIP解決)のゲートで、htu の再構成は保護しません

idp-server を信頼できないネットワークへ直接公開しないでください。

DPoPProofVerifier.verifyHtuポート比較を行います(従来 scheme / host / path のみ)。UriWrapper.equalsPort はデフォルトポート正規化済みなので https://h/phttps://h:443/p は一致し、https://h:8443/p は不一致です。非標準ポートで公開している場合は、クライアントが送る htu と一致するか確認してください。

8.3 DBマイグレーション(必須)

DPoP のために列を 2 本追加します。PostgreSQL / MySQL 両方に用意しています。

バージョン内容
V0_13_0_1__dpop_jwk_thumbprintoauth_tokenjwk_thumbprint VARCHAR(64)
V0_13_0_2__authorization_request_dpop_jktauthorization_requestdpop_jkt VARCHAR(255)

いずれも nullable な列の追加のみ(additive-nullable)で、既定値の指定もバックフィルもありません。PostgreSQL 11 以降は既定値なしの列追加がカタログ更新だけで済むため、oauth_token のような大きいテーブルでもテーブル書き換えは発生しません。MySQL は ALGORITHM=INSTANT の適用条件(列を末尾に追加する等)を満たすかを事前に確認してください。

旧アプリ × 新スキーマ

追加は additive-nullable で、INSERT は explicit-column です。したがって旧バージョンのアプリが新スキーマに対して動作します(ローリングデプロイ可)。

MySQL 側に IF NOT EXISTS が無いのは意図的です(ADD COLUMN IF NOT EXISTS は MariaDB 固有で MySQL 8.0 では構文エラーになります)。冪等性は Flyway のバージョン管理で担保しています。

8.4 その他

docker/nginx/nginx.conf の全 server ブロックで、BCP195 推奨 cipher のみ・TLS 1.3 ciphersuites を AES-GCM / CHACHA20-POLY1305 に制限しました。同梱の nginx をそのまま使っている環境のみ対象で、独自のロードバランサ / リバースプロキシを前段に置いている場合は影響しません。

8.5 動作確認

ケーステスト
DPoP 全般(RFC 9449)e2e/src/tests/spec/rfc9449_dpop.test.js / ciba_dpop.test.js
FAPI 2.0 プロファイルe2e/src/tests/spec/fapi2_0_security_profile*・OIDF Conformance Suite fapi2-security-profile-final
DPoP proof の alg 制限(RS256 拒否 / ES256 許可)DPoPProofVerifierTest
X-Forwarded-* のカンマ先頭値・Host 欠落フォールバックParameterTransformableTest
WWW-Authenticate のエスケープ・スキーム判定・401限定SecurityHeaderConfigurableTest

9. 認可画面と federation テンプレートの既定変更(Issue #1728 / #1759 / PR #1747 / #1760)

いずれもテンプレート・サンプル設定の変更です。テンプレートはセットアップ時に手動で流し込むもので、起動時に再適用される仕組みはありません。本バージョンへ更新しても既存テナントには反映されません。

対象BeforeAfter
e2e test-tenant / admin テナントの ui_confighttp://localhost:3000 + /signin/fido2/ / 未定義(同梱静的ページにフォールバック)https://auth.local.test${UI_BASE_URL})+ /signin/
admin テナントの cookie_same_siteNoneLax
cors_config.allow_originsauth.local.test 無しauth.local.test / api.local.test を追加
login-social テンプレートの federation redirect_uriバックエンド callback API(@PostMapping${UI_BASE_URL}/signin/sso-callback/
既存テナントは手動での確認が必要です

SameSite=NoneSecure 必須のため、use_secure_cookie: false との組み合わせではブラウザが AUTH_SESSION Cookie の保存自体を拒否し、認可フローが auth_session_mismatch で必ず失敗します。admin テナントをテンプレートから構築している場合は cookie_same_site を確認してください。

federation の redirect_uri がバックエンドの callback API を指していると、外部IdP(Google 等)からの GET リダイレクトが 405 になります。正しい着地先は app-view の sso-callback 画面で、そこが GET を受けて state から tenantId を復号し、バックエンドへ POST します。E2E はモック経由のため露見していませんでした。

config/scripts/update-admin-tenant.sh を追加してあります。テンプレートから再生成して tenant / authorization-server / client を PUT します(-d true で差分のみ確認)。

./config/scripts/update-admin-tenant.sh -d true   # 差分だけ確認
./config/scripts/update-admin-tenant.sh # 反映

このスクリプトはボディを常にテンプレート全体から組み立てます。authorization-server を GET → 編集 → PUT する方式にしないでください(6.1 の修正以前は署名鍵が消えました)。


10. 同意画面で配列クレームの要素を選べるようになった(Issue #1816 / PR #1817)

denied_claims はクレームを丸ごとしか落とせないため、ユーザーが複数持つもの(口座・カード・契約)を格納した custom_properties は all-or-nothing でした。claims:accounts に同意すると全口座が渡ります。End-User が選んだ要素だけをグラントに載せられるようにしました。

10.1 追加されたもの(🟢)

経路追加
view-data のレスポンスclaim_values — 選択候補。claims:* スコープが解放する custom_properties のうち配列のものだけ
/authorize のリクエストボディgranted_claim_values — 許可した要素。クレーム名をキー、値は要素の配列

絞る方向にしか働きません。 ユーザーが実際に保有する値との積集合だけが残るため、ボディに保有していない値を書いてもクレームには入りません。

キーを送らなければ従来どおり全要素が共有されるため、既存の構成の挙動は変わりません

10.2 view-data のレスポンスにキーが増える(🟡)

claim_values が増えます。返るのは次をすべて満たすときだけです。

  • 認証トランザクションがユーザーを解決済み(認証前は返りません。認可リクエストIDを取得しただけの相手にユーザー属性は渡りません)
  • authorization_server.extension.custom_claims_scope_mapping が有効
  • claims:* スコープに対応する custom_properties が空でない配列

レスポンスを厳密なスキーマで検証しているクライアントは追従が必要です(キーの追加のみ)。

10.3 granted_claim_values は許可リストです

列挙した要素はそのグラントに固定されます

granted_claim_values は「これらに限定する」という意思表示で、列挙したリストはグラントが生きているあいだ保持されます。同意後にユーザーの保有が増えても、列挙されていない要素は解放されません(新しい要素を解放するには再同意が必要です)。

全要素を列挙することは「制限しない」ではありません。 「いま保有している要素に限定する」になります。制限しないつもりであればキー自体を送らないでください

同梱の同意画面(app-view)は、ユーザーが1つ以上外したクレームについてのみ送ります。独自の同意画面を実装している場合は同じ扱いにしてください。

丸ごとの拒否は denied_scopesclaims:* スコープを外すことで表現します。denied_claims は custom property のクレームを止めません — クレーム発行はグラントのスコープを見るためです。

10.4 保存とローリングデプロイ

スキーマ変更・データ移行はありません。選択は「絞った結果」ではなく「絞る決定」としてグラントに保存され、既存のクレーム名 TEXT カラムに gcv:<base64url(JSON)> のセンチネルとして相乗りします(2 と同じく #1628 の RequestedVerifiedClaims の方式)。

決定を保存する形にしたのは、UserInfo がアクセストークンの sub からリポジトリでユーザーを引き直すためです。グラントが持つユーザーのスナップショットを絞る方式では UserInfo に届かず、同意画面で1口座だけ選んでも同じアクセストークンで UserInfo を叩けば全口座が取れてしまいます。

混在中は絞られない側に倒れます

クレーム発行は既知のクレーム名のマッチであってトークン集合の列挙ではないため、旧バージョンのインスタンスは未知の gcv: トークンを無視します。ローリングデプロイ中、新インスタンスが記録した選択は旧インスタンスが発行するトークンでは効かず、全要素が解放されます。

2.5 の内訳参照ポリシーとは倒れる向きが逆です(あちらは認証が完了しない側)。要素単位の同意を有効にするのは全インスタンスの更新完了後にしてください。

10.5 制約

  • オブジェクト要素は全体一致です。フィールドの集合で比較するため順序は問いませんが、{"id": "card-1"} のような識別子だけの部分指定では選択できません
  • 対象は custom_properties の配列のみです。roles / permissions / assigned_tenantsclaims:* で解放されますが、サーバーが決めるものなので対象外です
  • 全要素を拒否するとクレームごと落ちます(null や空配列ではなくキーが消えます。丸ごと拒否と同じ結果、OIDC Core §5.3.2 / #1699)

10.6 動作確認

ケーステスト
文字列配列・オブジェクト配列 × アクセストークン / ID Token / UserInfoe2e/.../usecase/advance/advance-22-claim-value-selection.test.js
認可エンドポイントが直接発行する ID Token(hybrid)同上
同意後に増えた要素が解放されない(許可リストの固定)同上
denied_claims に同名を指定しても選択が維持される同上
センチネル往復・要素一致の正規化・要素を剪定しないことGrantedClaimValuesTest / GrantClaimsSelectionTest(core unit)

10.7 関連

  • documentation/openapi/swagger-authentication-interaction-ja.yamlgranted_claim_values(リクエスト)/ claim_values(view-data)
  • config/templates/use-cases/login-password-only/EXPERIMENTS-claim-value-selection.md — 手元での確認手順

付録: 登録不要と判定した変更

🟢 追加 / ⚙️ 内部・運用のみで、既存の設定・クライアントの挙動を変えないと判定したもの。「未対応」と「そもそも該当が無い」を区別するために残す。

PR変更種別判定理由
#1735身元確認設定JSONから OpenAPI 仕様書を自動生成するスクリプト⚙️ドキュメント生成のみ。ランタイム不変
#1368パスワードレスFIDO2ユースケースの実験ガイド⚙️ドキュメント・スクリプトのみ
#1737FAPI 2.0 / DPoP follow-up クラスタA⚙️対象は fapi2-tenant のサンプル設定とドキュメント。既存テナント不変
#1751HttpRequestInputs 導入 + 全エンドポイント DTO を canonical 形へ⚙️DTO の公開アクセサ名を維持しており Handler / Verifier 層は無変更。リクエスト・レスポンスの形は不変(E2E フルスイート 2,052 件で確認)
#1766number-matching のプロトコル定義・OpenAPI・設定リファレンス⚙️既存実装のドキュメント化
#1769認可コードフローの認証を CIBA へ委譲する構成🟢新規テンプレート・E2E の追加。※出荷テンプレートの認証バイパスは 2.3 に登録済み
#1775mapping の JSONPath 述語の文書化と Jayway 挙動のテストピン留め⚙️ドキュメント + テストのみ
#1778使用中の Nimbus JOSE 挙動とラッパー契約のピン留め⚙️テストのみ
#1782mapping function に base64 を追加🟢新規関数。既存設定は参照しない
#1798http_requests を条件付きで実行できるようにする🟢condition は未設定なら無条件実行。既存設定は不変。単数 http_request では inert
#1805mapping function に normalize を追加🟢新規関数。既存設定は参照しない
#1812認可コードフロー + FIDO-UAF の全体像図と想定ケース⚙️ドキュメント + E2E
#1814相対リンクの階層ずれ 259 件を修正⚙️ドキュメントのみ
#1815step_definitions を interaction 単位で解決🟢既存の定義はいずれも interaction を持たないため method 単位の解決にフォールバックし、挙動は変わらない。toMapinteraction を持つときだけキーを出すため管理APIの往復も不変

付録の注記

  • external-api のステップは汎用サインイン画面(app-view)で描画できません(#1815)。interaction ごとの入力項目はテナント設定の request.schema で定義されるため、何を入力させるかは実行時までわからず、固定のコンポーネントでは覆えません。実装漏れではなく方式の性質です。
  • step_definitionsorder / user_identity_source / verification_source / registration_mode は未実装のままです(参照 0 箇所)。とくに order は現在無視されているため、実装すると順序が誤っている既存設定が失敗するようになります。将来のリリースで挙動変更になる候補です。