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

v0.14.0 影響確認

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

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

影響まとめ​

#変更種別対象
1セルフサービス連絡先(Email / 電話番号)の確認・変更を追加。DBマイグレーション 1 本(V0_14_0_1)が必要🟢 追加 / ⚙️全環境(マイグレーション必須)
1変更用スコープ email:change / phone:change を新設。テナントの scopes_supported とクライアントの scope の両方に追加しないと常に 403🟢 追加 / 🔒 要点検セルフサービス連絡先変更を使う構成
1管理権限 idp:contact-verification-challenge:read を新設🟢 追加 / 🔒 要点検権限を明示列挙しているロール(idp:* を持つロールは自動的に含む)
1管理APIが有効な verification_code を返す(監査ログには残さない)🔒 要点検管理APIの利用者・権限設計
1セキュリティイベント型が 16 種増えた({email,phone}_{verify,change}_{request_,}{success,failure})🟢 追加セキュリティイベントフック / SIEM 連携
1テナント取得のレスポンスに identity_policy_config.contact_change_policy が常に含まれるようになった🟡 挙動変更テナント設定を厳密なスキーマで検証している自動化・GET → 編集 → PUT を自分で書いている運用
1POST /v1/admin/operations/delete-expired-data のレスポンス内訳に contact_verification_challenge が増えた🟡 挙動変更当該レスポンスを厳密に検証している自動化
2組織配下の認証設定 API が要求する権限を idp:authentication-policy-config:* から idp:authentication-config:* に修正🔴 破壊的 / 🔒 要点検権限を明示列挙しているロール(idp:* を持つロールは影響なし)
3本人確認設定の GET が result / registration.response / registration.basic_auth を返すようになった🟡 挙動変更当該レスポンスを厳密なスキーマで検証している自動化
3本人確認設定の PUT で type を変更・省略すると 400(従来は 200 で無反映)🔴 破壊的PUT に毎回新しい type を入れている自動化
3本人確認設定の enabled が実際に効くようになった🔴 破壊的enabled: false を書いている設定(既定は true)
4管理APIのユーザー検索で preferred_username が完全一致になった(従来は部分一致)。部分一致は preferred_username_like へ分離🔴 破壊的ユーザー一覧APIを preferred_username の部分一致で呼んでいる管理画面・自動化
4ユーザー検索に preferred_username_like(部分一致)を追加🟢 追加断片で検索したい構成
5Spring Boot を 4.0.8 から 4.1.1 に更新(4.0 系は 2026-12-31 に OSS サポート終了)⚙️全環境(イメージの更新のみ)。Spring に依存する自作プラグインを組み込んでいる構成は要確認
6FAPI 1.0 Advanced / FAPI 2.0 で openid スコープの無い認可リクエスト(JARM・PAR)にも、redirect_uri の必須・事前登録・完全一致を適用🔴 破壊的openid なしの FAPI リクエストで redirect_uri を省略している、または登録と異なる値を送っているクライアント
7FAPI のクライアント認証の要件(認証方式・署名アルゴリズム・鍵長・client secret の長さ・aud)を、PAR と refresh token のリクエストにも適用🔴 破壊的FAPI のスコープで PAR / refresh token を使い、要件を満たさない認証をしているクライアント
7FAPI のクライアント認証の要件を満たさないときのエラーを invalid_client に統一し、トークンエンドポイントでは 401 にした(従来は 400、認証方式の制限は unauthorized_client)🟡 挙動変更ステータスコードやエラーコードで分岐しているクライアント
7client_assertion_type が urn:ietf:params:oauth:client-assertion-type:jwt-bearer 以外なら invalid_client(トークンエンドポイント・PAR とも 401。従来は値を見ていなかった)🔴 破壊的private_key_jwt / client_secret_jwt で誤った client_assertion_type を送っているクライアント
7FAPI 1.0 Advanced で RSA1_5 を拒否(Request Object の暗号化、id_token_encrypted_response_alg / authorization_encrypted_response_alg)🔴 破壊的RSA1_5 を設定・使用しているクライアント
7client_id_alias で認証する FAPI 1.0 Baseline(FAPI-CIBA を含む)のクライアントが、トークンリクエストで拒否されなくなった🟡 挙動変更なし(従来は常に失敗していた)
7UserInfo が x-fapi-interaction-id を返す。アプリケーション API のログ(MDC)に fapi_interaction_id を出す🟢 追加ログの項目を厳密に扱っている収集基盤
8認可拒否(POST .../deny)のエラー応答のキーを errorDescription から error_description に修正🟡 挙動変更認可拒否のエラー応答から errorDescription を読んでいる認可画面
9認可画面を別サイトに置く構成を追加(ui_config.cross_site / クライアントの extension.cross_site_authorization_view、既定は無効)。GET /{tenant-id}/v1/authorizations/{id}/complete を追加🟢 追加認可画面を idp-server と別サイトに置く構成。宣言しない限り挙動は変わらない
9付属の認可画面(app-view)が、応答に auth_proof があれば authorize に載せ、/complete を経由するようになった🟢 追加app-view をそのまま使う構成(別サイト認可画面モードを有効にしない限り挙動は変わらない)
9CORS の応答の Access-Control-Allow-Headers に、テナントの allow_headers の設定に関係なく x-view-binding を含めるようにした(別サイト認可画面モードで認可画面が送るヘッダー)⚙️なし(ヘッダーを許可するだけ。設定の変更は不要)
10リクエストオブジェクトの独自クレームをカスタムパラメータとして読む。クエリと同じキーはリクエストオブジェクトの値を使う🟡 挙動変更リクエストオブジェクトに独自クレームを入れているクライアント、view-data の custom_params を読む認可画面
10FAPI 1.0 Advanced では、カスタムパラメータもリクエストオブジェクトの値だけを使う(クエリだけで渡した値は使われない)🔴 破壊的クエリでカスタムパラメータ(画面の振り分けの view_version など)を渡している FAPI Advanced のクライアント
10認可画面へのリダイレクト URL に、リクエストオブジェクトの値を載せない。リクエストオブジェクトを含む PAR では、カスタムパラメータを URL に載せない(view-data には載る)🟡 挙動変更リダイレクト URL のクエリからカスタムパラメータを読んでいる認可画面
11認証ポリシーの条件式で、認可リクエストのカスタムパラメータを $.request.custom_params.* として参照できる。信頼する出どころは custom_params_trusted_sources(既定 pushed / request_object)で選ぶ。右辺を path で書く value_path(eq / ne)を追加🟢 追加RP が渡した値(会員番号など)とログインした利用者の属性を照合したい構成
11認証ポリシーの登録・更新で、value_path の誤用(value と併記、eq / ne 以外)と custom_params_trusted_sources の未知の値を 400 にする🟢 追加なし(新しい項目の検査のみ)
11認証トランザクションの context に custom_params を保存する。端末向けの認証トランザクション API には含めない⚙️管理 API の認証トランザクションの応答を厳密に検証している自動化
12DPoP proof と Client Attestation PoP JWT の iat の許容範囲を ±5 分から ±60 秒に短縮(dpop_proof_acceptable_window_seconds / client_attestation_pop_acceptable_window_seconds で変更可。ABCA は v0.14.0 で追加)🟡 挙動変更時計が 60 秒以上ずれる端末、iat を古いまま使い回しているクライアント
12受け付けた DPoP proof / PoP JWT の jti を Redis に記録し、同じ proof の 2 回目を拒否(DPoP は invalid_dpop_proof、userinfo は 401 invalid_token、ABCA は invalid_client_attestation。ABCA は v0.14.0 で追加)🟡 挙動変更同じ proof を再送しているクライアント(リトライで proof を作り直していないもの)
12private_key_jwt / client_secret_jwt の client assertion の jti も Redis に記録し、同じ assertion の 2 回目を 401 invalid_client で拒否(Issue #1902)🟡 挙動変更同じ client assertion を複数のリクエストで使い回しているクライアント(リトライやトークン・PAR・introspection への流用を含む)
12client assertion を受け入れる期間を最長 60 秒に制限(iat があれば iat から 60 秒、無ければ exp が 60 秒以内。client_assertion_max_lifetime_seconds で変更可、Issue #1902)🔴 破壊的iat から 60 秒より後に assertion を使っているクライアント、iat を付けずに exp を 60 秒より先にしているクライアント
13同意画面での配列の要素の選択(granted_claim_values)が、verified_claims のクレームにも効く。view-data の claim_values に verified_claims.claims の候補が増えた🟢 追加verified_claims の配列クレームを出している構成。選択を送らない限り挙動は変わらない。ローリングデプロイの途中は、古いノードでは選択が効かない
13付属の認可画面(app-view)の同意ステップで、verified_claims の配列クレームの要素も選べるようになった🟢 追加app-view をそのまま使う構成
13view-data の claim_values を、認証が成功したあとにだけ返すようにした(従来は、login_hint や多段認証の 1 段目でユーザーが決まった時点で返していた)🟡 挙動変更認証が終わる前に claim_values を読んでいる独自の認可画面(付属の app-view は同意ステップでだけ読むので影響なし)
14binding message の照合(authentication-device-binding-message)で、照合する値を持たないトランザクションは 400 invalid_request(従来は空の binding_message を送ると成功していた)🟡 挙動変更binding_message を付けない CIBA リクエストで、このステップを認証ポリシーに入れている構成
15監査ログの管理 API(システム・組織)の応答で、request / before / after などに含まれる秘密の値(パスワード・シークレット・トークン・鍵など)を [SCRUBBED] に置き換える。記録されている内容は変わらない🟡 挙動変更監査ログの API の応答から、これらの値を読んでいる運用ツール
16client_attestation_trust_source: x5c で、client_attestation_trusted_root_certificates に途中の CA を置けるようにした。あわせて x5c の検証を RFC 5280 のパス検証にし、リーフが CA の証明書や、issuer と subject がつながらないチェーンを拒否する🟢 追加なし(attest_jwt_client_auth は v0.14.0 で追加。§22)
17Introspection(introspection / introspection-extensions)で、呼び出し元のクライアント認証の失敗を 401 にし、応答から active と status_code を外した(従来は 400 + active: false)🔴 破壊的Introspection の 400 や active: false で分岐している Resource Server(資格情報が壊れているときだけ)
17PAR のクライアント認証の失敗を 400 から 401 にした(トークンエンドポイントと同じ)🟡 挙動変更PAR のステータスコードで分岐しているクライアント
17client_secret_basic で失敗したとき、WWW-Authenticate: Basic realm="<issuer>" を付ける(トークン・revocation・introspection・PAR・CIBA)🟢 追加なし
17client_secret_basic 以外で登録したクライアントが Basic ヘッダーも送ると、認証に失敗する(RFC 6749 §2.3。従来は登録した方式だけで認証していた)🔴 破壊的ボディやアサーションと一緒に Basic ヘッダーも送っているクライアント(SDK の設定による)
17存在しない client_id を invalid_client の 401 にした(従来は revocation・CIBA が 400、introspection が 400 + active: false、PAR が 400 invalid_request)🟡 挙動変更これらのステータス・エラーコードで分岐しているクライアント
18身元確認の process に caller(end_user / external_service)を追加。呼べない入口からは登録されていない process と同じ 404🟢 追加なし(書かない限り、下の行以外の挙動は変わらない)
18caller を書いていない process でも、request.basic_auth があればコールバック(/internal/...)からだけ実行できる。エンドユーザー向けの入口(/v1/me/...)から呼ぶと 404🔴 破壊的basic_auth を設定した process を、エンドユーザー向けの入口からも呼んでいる構成
18身元確認設定の登録・更新で、caller の値が end_user / external_service 以外か、end_user に request.basic_auth(username と password の両方)を書いた設定を 400 にする🟢 追加なし(新しい項目の検査のみ)
18同梱のテンプレート・設定例の evaluate-result(ongoing の callback-result も)を caller: external_service にした。承認はコールバック(/internal/.../{id}/evaluate-result)から返す🔴 破壊的テンプレートから作った設定を入れ直し、エンドユーザー向けの入口から evaluate-result を呼んでいる構成
18登録済みの設定は自動では変わらない。外部サービスが結果を返す process には caller: external_service を書く(同梱のテンプレートから作った設定は evaluate-result など)🔒 要点検身元確認設定を登録している構成
18ongoing テンプレートの callback-result で、process の直下にあって効いていなかった required_processes を dependencies に移し、process_sequence を足した。apply より前のコールバックは 400 pre_hook_validation_failed(再送は従来どおり受け付ける)🟡 挙動変更ongoing テンプレートから作った設定を入れ直し、apply より前にコールバックを送る外部サービス
19申込みを ID で扱う API(/v1/me/... の process 実行・削除、コールバック)で、パスの verification-type が申込みの種別と違えば、申込みが見つからないときと同じ 404 にする(従来は種別を見ずに処理していた)🔴 破壊的申込みと違う種別のパスで呼んでいるクライアント・外部サービス
19同梱のテンプレート・設定例から、読まれていなかったキー(トップレベルの external_service / external_application_id_param、common.external_service)を外した⚙️なし(読まれていなかったので挙動は変わらない)
20マッピングの to: "*" で展開したキーを、パスとして読まずにそのまま使う(従来は a.b を入れ子に、0 などを配列の添字として扱っていた)🟡 挙動変更"*" で展開する元のデータに . や数字だけのキーがあり、入れ子になる動きを使っている設定
20マッピングの to の配列の添字を 999 までにした。超えると、そのルールが動いたときに設定の誤りとしてエラー(多くの入口で 500)🟡 挙動変更to に 1000 以上の添字を書いている設定
20身元確認の process_sequence で断られた呼び出し(前提プロセスが未完了・ステータスが対象外・再実行)を、process の実行として記録しない。前提を満たしたあとに allow_retry: false の process を呼べる🟡 挙動変更断られた呼び出しが processes の回数に入ることを前提にしている運用
20身元確認の allow_during_statuses を、申込み ID なしで呼ぶと 400(従来は 500)。値はステータスの小文字(requested など)だけを受け付け、それ以外の設定は登録・更新で 400🔴 破壊的allow_during_statuses に大文字などの値を書いた設定(従来は一致せず、常に拒否されていた)
21デバイスの登録・削除と、2 要素目(allow_registration: true)での連絡先の登録は、認証済みのユーザーに限る。login_hint で指定しただけのユーザーでは unauthorized🔴 破壊的login_hint だけでユーザーを決めて、デバイス登録や連絡先の登録に進ませている構成(ログイン済みのユーザーを新しい認可リクエストで登録に誘導する場合を含む)
21FIDO2 / FIDO-UAF の登録と FIDO2 の削除の成功を、認証として数えない(amr・auth_time・amr から決まる acr・level_of_authentication_scopes)🔴 破壊的認証のあとにデバイスを登録して認可を終える構成(CIBA では登録だけの構成も)で、amr / acr に fido2 / fido-uaf が入ることを前提にしている RP・ポリシー、成功条件(success_conditions)を書いていない /me 用の登録のポリシー(削除は完了扱いにならないだけ)
21外部 API 認証・パスワード・SMS・Email で $.user を外部に渡すときの「認証済み」の判定が、上の 2 行に合わせて変わる。/me の API では最初の interaction から渡る🟡 挙動変更/me の API で、外部 API などに $.user が渡らないことを前提にしている構成
21外部 API 認証(external-api-authentication)とナンバーマッチングの成功は、これまでどおり認証として数える🔒 要点検これらで本人を確かめていない構成(device_registration_conditions で登録の条件を絞る)
21FIDO2 の登録解除で、credential の持ち主をトランザクションのユーザーと照合する。リクエストの username は使わない。持ち主でなければ、存在しないときと同じ 400(本文の credential_not_found は $.error を写す設定のときだけ)。credential_id が無いときは 500 でなく 400🟡 挙動変更登録解除のリクエストに username を入れている構成、登録解除を http_request で外部に渡す設定($.request_body を写すと username がユーザーから決めた値になる)
21FIDO-UAF の登録解除で、device_id がトランザクションのユーザーのデバイスか確かめる。違うとき・device_id が無いときは、FIDO サーバーに送らず 400 invalid_request🟡 挙動変更ユーザーのデバイス一覧にない device_id で登録解除している構成
21認証設定の post_hook に verifications(type: match)を追加。評価するのは FIDO-UAF の認証(fido-uaf-authentication)だけで、FIDO サーバーが返す認証したデバイスとチャレンジの device_id を照合できる🟢 追加 / 🔒 要点検FIDO-UAF の認証を使う構成(FIDO サーバーが認証したデバイスの ID を返すなら、照合の設定を推奨)
22attest_jwt_client_auth(Attestation-Based Client Authentication)を追加。DBマイグレーション 3 本(V0_14_0_2〜V0_14_0_4)が必要🟢 追加 / ⚙️全環境(マイグレーション必須)
22管理権限 idp:client-instance:{create,read,revoke,delete} を新設🟢 追加 / 🔒 要点検権限を明示列挙しているロール(idp:* を持つロールは自動的に含む)
22セキュリティイベント型が 4 種増えた(client_instance_registration_{challenge_issued,success,failure}・client_instance_revoked)🟢 追加セキュリティイベントフック / SIEM 連携
22POST /v1/admin/operations/delete-expired-data のレスポンス内訳に client_instance_registration_challenge が増えた🟡 挙動変更当該レスポンスを厳密に検証している自動化
22payload を claims として読めない JWT への応答を、500 から入口ごとのエラーにした(DPoP は invalid_dpop_proof、JWT Bearer は invalid_grant、client_assertion はクライアント認証の失敗、それ以外は 400 invalid_request)🟡 挙動変更なし(壊れた JWT への応答コードだけ)
23認可エンドポイントでの認証で、同じユーザーの OP セッションがあるとき、OP セッションの auth_time / acr / amr を今回の認証の値に置き換える(acr が下がる場合も置き換える)🟡 挙動変更SSO の ID Token の auth_time / acr / amr を見ている RP、強い認証のあとに弱い認証をさせる構成
23TIME_ZONE が UTC 以外のとき、SSO の max_age の判定が時差の分ずれていたのを修正。更新前の OP セッションからの SSO では、ID Token の auth_time が時差の分ずれる🟡 挙動変更TIME_ZONE を UTC 以外にしている環境(UTC より東は従来より厳しく、西はゆるく判定される)
24authentication-cancel を、AUTH_SESSION(cookie)の照合にかけない(認証端末からも認可画面からも)🟡 挙動変更なし(従来は auth_session_binding_required: true(既定)で 401 auth_session_mismatch になり、端末から取り消せなかった)
25/v1/me/mfa/{mfa-operation-type} で oauth / ciba を指定すると 400 invalid_request🟡 挙動変更なし(従来はトランザクションを作れたが、その後の操作が 500 になり使えなかった)
26外部に渡す $.user に external_user_id(値があるとき)が増えた🟡 挙動変更$.user を丸ごと写して外部に送っている設定
27MySQL 8.0 で V0_10_0_3 / V0_10_0_4 が構文エラーで止まっていたのを、実行できる書き方に直した⚙️MySQL で、V0_10_0_3 の失敗が記録されている DB(flyway repair で失敗の記録を消してから migrate)

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


1. セルフサービス連絡先の確認・変更​

認証済みエンドユーザーが自分のメールアドレスまたは電話番号を確認・変更する API を追加した(#1876 / Issue #1416)。

POST /{tenant-id}/v1/me/{email|phone}/verification            スコープ openid
POST /{tenant-id}/v1/me/{email|phone}/verification/{id}/verify スコープ openid
POST /{tenant-id}/v1/me/{email|phone}/change スコープ {channel}:change
POST /{tenant-id}/v1/me/{email|phone}/change/{id}/verify スコープ {channel}:change

詳細は セルフサービス 連絡先の確認・変更。

1.1 DBマイグレーション​

⚙️ 全環境で必須。

ファイル内容
libs/idp-server-database/postgresql/V0_14_0_1__contact_verification_challenge.sqlcontact_verification_challenge テーブル + index 2 本 + RLS
libs/idp-server-database/mysql/V0_14_0_1__contact_verification_challenge.mysql.sql同(MySQL に RLS は無いため tenant_id 述語のみ)

新規テーブルの追加のみで、既存テーブルへの変更は無い。

1.2 変更用スコープ​

🔒 要点検。 確認(/verification)は openid で足りるが、変更(/change)は専用スコープを要求する。

"scopes_supported": ["openid", "profile", "email", "email:change", "phone:change"]

テナントの scopes_supported とクライアントの scope の両方に追加する必要がある。クライアント登録スコープの allow-list フィルタが効くため、片方だけではトークンに載らず、変更エンドポイントが常に 403 insufficient_scope になる(fail closed)。

分離した理由は、確認と変更で動くものが違うため。

確認変更
変わるもの*_verified のみ値 +*_verified(+ 対応する identity policy なら preferred_username)
送信先現在値に固定リクエストの new_value

変更を openid で通すと、第三者クライアントに発行した通常の OIDC トークンにも識別子の移動能力を与えることになる。

1.3 管理権限​

🔒 要点検。 サポート照会用の管理API に idp:contact-verification-challenge:read を新設した。

GET /v1/management/tenants/{tenant-id}/contact-verification-challenges
GET /v1/management/tenants/{tenant-id}/contact-verification-challenges/{id}
GET /v1/management/organizations/{org-id}/tenants/{tenant-id}/contact-verification-challenges
GET /v1/management/organizations/{org-id}/tenants/{tenant-id}/contact-verification-challenges/{id}

権限照合は PermissionMatcher のワイルドカード方式なので、idp:* を持つロール(既定の administrator)は自動的に含む。影響を受けるのは権限を明示列挙しているロールだけで、その場合は追加が必要。

1.4 管理APIが有効な検証コードを返す​

🔒 要点検。

{ "target_value": "...", "delivery": "internal", "verification_code": "123456", "attempts": 0, "expired": false }

「コードが届かない」という問い合わせに答えるための API なので、実際の送信先と有効なコードを返す。チャレンジの消費には本人のアクセストークンが要るため、コードだけを入手したオペレーターが単独で確定させることはできない。

監査ログは永続なので、after ペイロードからは verification_code と external_reference を落としている。

委譲構成(外部サービスがコードを生成する場合)は delivery: "external" になり、verification_code の代わりに external_reference を返す。

1.5 セキュリティイベント型の追加​

🟢 追加。 16 種増えた。

{email,phone}_{verify,change}_request_{success,failure}
{email,phone}_{verify,change}_{success,failure}

ログイン時の email_verification_* とは別物で、こちらは意図的なプロフィール操作を記録する。確認と変更を分けてあるのは、変更だけがログイン識別子を動かすため。

フック / SIEM 側でイベント型を allow-list している場合は追加が必要。

1.6 テナント設定に contact_change_policy が増えた​

🟡 挙動変更。 identity_policy_config に contact_change_policy が加わり、テナント取得のレスポンスに常に含まれる(未設定でも既定値が出る)。

"identity_policy_config": {
"identity_unique_key_type": "EMAIL_OR_EXTERNAL_USER_ID",
"contact_change_policy": {
"identifier_move": { "allowed": true, "notify_previous_value": true },
"attribute_only": { "allowed": true, "notify_previous_value": true }
}
}

影響するのは 2 パターン。

  • テナント設定のレスポンスを厳密なスキーマで検証している自動化 → 未知キーで落ちる可能性
  • GET → 編集 → PUT を自分で書いている運用 → 既定値がそのまま保存されるようになる

既定で効くのは notify_previous_value: true のみで、変更の重さは既定では規定されない(スコープだけが門番)。識別子が動くテナントは authentication_conditions / max_auth_age_seconds を明示的に書くことを推奨する。

allowed が掛かるのは変更だけで、確認(/verification)には掛からない。確認は値を置き換えず *_verified を立てるだけのため。authentication_conditions / max_auth_age_seconds は確認にも掛かる。

1.7 delete-expired-data の内訳​

🟡 挙動変更。 POST /v1/admin/operations/delete-expired-data のレスポンスに contact_verification_challenge が加わった。

{ "deleted": { "...": 0, "contact_verification_challenge": 47 }, "total_deleted": 47 }

期限切れチャレンジは他の有効期限付き 7 テーブルと同じ入口で回収される。件数を固定長で扱っている自動化は影響を受ける。

1.8 既存の認証設定への影響​

影響なし。 送信には既存の type: "email" / type: "sms" 認証設定をそのまま流用するが、

  • 追加した resend_cooldown_seconds を読むのはセルフサービス側だけで、ログイン OTP 側は retry_count_limitation / expire_seconds しか読まない
  • templates の {email,phone}_{verify,change} / *_notice は未定義ならそれぞれの既定にフォールバックするため、既存テンプレートの変更は不要(ただし *_notice だけはフォールバックせずスキップする。理由)
  • details は生の Map のまま往復するため、保存済み設定に影響しない

認証ポリシー(flow 設定)の追加も不要。


2. 組織配下の認証設定 API の権限を修正​

🔴 破壊的変更。 組織配下の認証設定 API が、認証ポリシーの権限で守られていた(Issue #1898)。

/v1/management/organizations/{organization-id}/tenants/{tenant-id}/authentication-configurations
修正前修正後
組織配下idp:authentication-policy-config:{create,read,update,delete}idp:authentication-config:{create,read,update,delete}
システム管理用idp:authentication-config:*(変更なし)

同じリソースを扱う 2 本の API で要求する権限が食い違っていた。組織パスから来たというだけで、認証設定が持つもの(外部認証サービスの接続先と資格情報を含む)は変わらない。

2.1 影響を受けるロール​

idp:* を持つロール(既定の administrator)は影響を受けない。権限を個別に列挙しているロールだけが対象。

そのロールが持つ権限修正前修正後
idp:authentication-config:* のみ組織配下を操作できなかった(403)操作できる
idp:authentication-policy-config:* のみ認証ポリシーに加えて認証設定も操作できた認証設定は 403

後者は権限の昇格にあたるため、修正後に 403 になるロールがあっても、それが本来の状態である。アップグレード前に、認証ポリシー権限だけで組織配下の認証設定を操作している運用がないか確認すること。必要なら idp:authentication-config:* を明示的に付与する。

2.2 ダッシュボードとの整合​

idp-server-dashboard は認証設定の画面を idp:authentication-config:read で出し分けている。この修正で、画面の出し分けとサーバーの判定が一致する。


3. 本人確認設定の往復と type / enabled​

本人確認設定(identity-verification-configurations)の管理 API で、GET が保存内容の一部を返さず、GET → 編集 → PUT で設定が消えていた(Issue #1900)。あわせて PUT の type と enabled の扱いを直した。システム管理用・組織配下の両方が対象。

3.1 GET が返す項目が増えた​

🟡 挙動変更。 DB には保存されていたが GET が返していなかった項目を返すようにした。

項目修正前修正後
resultverified_claims_mapping_rules が空だとキーごと無し何か設定されていれば返る
registration.response常に無し返る
registration.basic_auth常に {}username / password を返す

result が落ちていたのは、出力条件が verified_claims_mapping_rules の有無だけを見ていたため。user_status / user_claims_mapping_rules / custom_properties_mapping_rules / 各 *_update_policy しか持たない設定は、保存されていても一度も返らなかった。

GET のレスポンスにキーが増えるため、厳密なスキーマで検証している自動化は影響を受ける。

3.2 PUT の type が変更不可になった​

🔴 破壊的変更。

PUT ... {"type": "<保存済みと違う値>"}   → 400 type cannot be changed
PUT ... {} (type 省略) → 400 type is required

修正前は 200 が返るのに何も更新されなかった。UPDATE 文が WHERE id = ? AND type = ? AND tenant_id = ? でリクエスト由来の新しい type を条件に使うため 0 行更新になり、それでもレスポンスは変更後の姿を返していたので、呼び出し側から成功と区別がつかなかった。

リネームを通す方向にはしていない。type は実行時に設定を引く際のキーであり、identity_verification_application.verification_type が記録している値でもあるため、変えると旧名で積まれた申込みが宙に浮く。

PUT に毎回新しい type を入れている自動化は 400 になる。 GET の結果をそのまま PUT する使い方なら影響はない。

3.3 enabled が効くようになった​

🔴 破壊的変更。 ペイロードの enabled を DB のカラムに書くようにした。

修正前は INSERT / UPDATE ともカラムに触れておらず、DDL の DEFAULT TRUE のままだった。実行時の取得は AND enabled = true でカラムを見るため、enabled: false を書いても設定は有効なままだった。

既定は true(リクエスト・ドメインオブジェクトの両方)なので、enabled を書いていない設定は影響を受けない。enabled: false を明示している設定は、アップグレード後に実際へ無効化される。

無効化されるのが意図どおりか、アップグレード前に確認すること。

SELECT tenant_id, type FROM identity_verification_configuration WHERE payload->>'enabled' = 'false';

既存行のカラム値は移行しない。次にその設定を PUT したときにペイロードの値がカラムへ反映される。


4. 管理APIのユーザー検索で preferred_username が完全一致になった(Issue #1866 / PR #1867)​

ユーザー一覧API(GET .../users)の preferred_username は前後ワイルドカードの部分一致でした。本バージョンから完全一致になります。

BeforeAfter
照合部分一致(大文字小文字区別なし)完全一致
?preferred_username=ichiroichiro-ito も ito.ichiro もヒットヒットしない
?preferred_username=ichiro-itoヒットヒット
?preferred_username_like=ichiro(パラメータ自体が無い)ヒットする(従来の挙動と同一)

4.1 影響(🔴 破壊的)​

部分一致で呼んでいる管理画面・自動化は結果が変わります。 エラーにはならず、0 件が返る形で現れます。

preferred_username はテナントとプロバイダーの範囲で一意な識別子(uk_preferred_username)で、部分一致で探す対象ではありません。同じクラスの値は元から完全一致でした。

値照合
email / phone_number / external_user_id / user_id元から完全一致
preferred_username部分一致だった(本変更で完全一致へ)
name / given_name / family_name / middle_name / nickname部分一致のまま(変更なし)
role / permission部分一致のまま(変更なし)

認証時の引き当て(findByPreferredUsername)は元から完全一致で、検索だけが例外になっていました。

空の値にも注意してください。 ?preferred_username=(値が空)は、従来は前後ワイルドカードで全件に一致していましたが、本バージョンからは空文字との完全一致になり 0 件が返ります。検索フォームの全項目を空のまま送る管理画面は、未入力の項目を送らないようにしてください。空の値で 0 件になるのは、元から完全一致の email などと同じ挙動です。

4.2 移行手順​

部分一致が必要なら preferred_username_like へ送り先を変えてください。 挙動は従来の preferred_username と同一(前後ワイルドカード・大文字小文字区別なし)です。

- GET .../users?preferred_username=ichiro
+ GET .../users?preferred_username_like=ichiro

管理画面が検索ボックスの入力をそのまま送っている場合、送り先のパラメータ名を変えるだけで運用者の体験は変わりません。

そのうえで、識別子として完全な値で引く呼び出し(自動化・API 直叩き)は preferred_username を使ってください。provider_id もあわせて渡すと一意キー uk_preferred_username (tenant_id, provider_id, preferred_username) が最後まで効き、桁違いに速くなります(4.4)。

人名で探す用途であれば name / given_name / family_name / nickname も部分一致のままです。

preferred_username_like は速くなりません

一意キーに対する前後ワイルドカードの部分一致は索引が効かず、テナントの全ユーザーを評価します。これは従来と同じコストです。名前を分けた目的は、遅い経路を明示的な選択にすることであって、部分一致自体を速くするものではありません。

4.3 変わらないもの​

  • 他の検索パラメータの照合方式。上の表のとおり、変更したのは preferred_username のみです
  • DB マイグレーション。索引の追加はありません。provider_id を渡さない完全一致は一意キーの先頭列(tenant_id)までしか絞れないため、(tenant_id, preferred_username) の索引を Issue #1910 で追加する予定です
  • レスポンスの形。list / total_count / limit / offset は同じです
  • 部分一致そのものの挙動。preferred_username_like は従来の preferred_username と同じ照合をします

4.4 なぜ変えたか​

100 万行・平均行幅 1058 bytes・heap 1121MB での実測(performance-test/db-bench/bench_user_search.sh)。

クエリBefore(部分一致)After(provider_id なし)After(provider_id あり)
COUNT337〜903 ms28〜145 ms0.11 ms
一覧828〜1229 ms35 ms0.08 ms

一意キーは (tenant_id, provider_id, preferred_username) の順で、PostgreSQL 15 は途中の列を飛ばして探索できません。そのため provider_id を渡さない完全一致は、索引上でテナントの全エントリを読みます(それでも部分一致より 1 桁速い)。provider_id なしでも一意キー並みに引けるよう、Issue #1910 で索引を追加します。

部分一致は前後ワイルドカードのため索引が原理的に効かず、ヒット件数に関係なくテナントの全行を評価していました。一覧APIは COUNT を先に実行してから一覧を引くため、1 回の検索でスキャンが 2 回走ります。

4.5 動作確認​

ケーステスト
完全一致で当たる / 部分一致では当たらないe2e/.../organization/organization_user_search_preferred_username.test.js
preferred_username_like が部分一致で当たる / 大文字小文字を区別しない同上

5. Spring Boot 4.1 への更新​

⚙️ 内部・運用。 Spring Boot を 4.0.8 から 4.1.1 に上げた(Issue #1886)。4.0 系の OSS サポートは 2026-12-31 に終わるため、セキュリティ修正を受け続けられるようにするのが目的。

5.1 主な依存ライブラリの版​

Spring Boot が管理する版のうち、変わったものは次のとおり。

ライブラリ4.0.84.1.1
Spring Security7.0.77.1.1
Spring Session4.0.54.1.1
Micrometer1.16.71.17.1

Spring Framework(7.0.9)・Tomcat(11.0.24)・Jackson(3.1.5)・Logback(1.5.38)は変わらない。公開済みのセキュリティアドバイザリ(2026-08-20 分)は 4.0.8 / 4.1.1 のどちらにも取り込まれている。

5.2 変わらないもの​

  • API・設定・DB:idp-server のコードの変更は無い。Spring Boot 4.0 で非推奨になった API は使っておらず、application.yaml のプロパティにも 4.1 での名前変更・削除は無い(spring-boot-properties-migrator で起動時に確認)
  • ビルド・実行環境:Gradle 8.14 / JDK 21 のまま(4.1 の要件は Gradle 8.14 以上、Java 17 以上)

5.3 確認が必要な構成​

Spring Boot 4.1 のリリースノート(Deprecations from Spring Boot 4.0)のとおり、4.0 で非推奨になったクラス・メソッド・プロパティが 4.1 で削除された。あわせて layertools の jar mode も削除されている。

Spring Boot の API に依存する自作プラグインを組み込んでいる構成は、リリースノートで削除・変更された API を使っていないか確認し、プラグインを 4.1 でビルドし直して動作を確認すること。

Micrometer は 1.16 から 1.17 に上がるが、idp-server はメトリクスを外部へ送る設定(Prometheus / OTLP などのレジストリ)を持たず、actuator が公開するのも health / info のみのため、既存の監視には影響しない。独自にレジストリを追加している構成は、メトリクス名・タグの変化を確認すること。


6. FAPI の redirect_uri 検証を openid なしのリクエストにも適用​

🔴 破壊的変更。 FAPI 1.0 Advanced と FAPI 2.0 Security Profile で、openid スコープを含まない認可リクエストにも redirect_uri の検証を適用するようにした(Issue #1902)。

FAPI 1.0 Baseline 5.2.2-8/9/10(Advanced が引き継ぐ)は、redirect_uri の必須・事前登録・登録値との完全一致を求めている。FAPI 2.0 は、必須を 5.3.2.2-6 で求め、事前登録と完全一致は OAuth 2.0 の登録(RFC 6749 3.1.2.3)と open redirector の禁止による。これまでは openid スコープを含むリクエスト(OIDC の検証経路)にだけ適用されていて、含まないリクエスト(JARM を使う Advanced、openid なしの FAPI 2.0 の PAR など)には適用されていなかった。FAPI 1.0 Baseline は以前から両方に適用している。

修正前修正後
redirect_uri の省略登録済みの先頭の URI を使うエラー(invalid_request)
未登録・登録値と異なる redirect_uri受理エラー(invalid_request)
エラーの返し方一部のエラーは redirect_uri へリダイレクトredirect_uri の検証エラーは認可サーバーのエラー画面(PAR は 400)

6.1 影響を受ける構成​

openid スコープを含まない FAPI 1.0 Advanced / FAPI 2.0 のリクエストで、次のどちらかに当てはまるクライアント。

  • redirect_uri を省略している(request object・PAR のパラメータに含めていない)
  • 登録済みの redirect_uri と文字列が完全に一致しない値を送っている

openid スコープを含むリクエストは、以前から同じ検証を受けているため影響しない。

6.2 確認​

ケーステスト
FAPI 2.0:openid なしの PAR で未登録・省略の redirect_uri を拒否、直接の認可リクエストのエラーを未登録の redirect_uri へ返さないe2e/src/tests/spec/fapi2_0_security_profile.test.js
FAPI 1.0 Advanced:openid なし(JARM)で未登録・省略の redirect_uri を拒否、エラーを未登録の redirect_uri へ返さないe2e/src/tests/spec/fapi_advance.test.js

7. FAPI のクライアント認証の要件を PAR と refresh token にも適用​

🔴 破壊的変更。 FAPI の各プロファイルのクライアント認証の要件を、認可コードのトークンリクエストだけでなく、クライアントが認証するすべてのリクエスト(PAR、refresh token)に適用するようにした(Issue #1902)。refresh token のリクエストのプロファイルは、更新する grant のスコープから決める。

要件FAPI 1.0 BaselineFAPI 1.0 AdvancedFAPI 2.0
認証方式client_secret_basic / client_secret_post を拒否(5.2.2-4)client_secret_* と public client を拒否(5.2.2-14/16)client_secret_* と public client を拒否(5.3.2.1)
client assertion の署名アルゴリズム―PS256 / ES256(8.6)PS256/384/512・ES256/384/512・EdDSA(5.4.1)
鍵長RSA 2048 bit・EC 160 bit 以上(5.2.2-5/6)同左RSA 2048 bit・EC 224 bit 以上(5.4.1)
client_secret_jwt の secretHS256 は 32・HS384 は 48・HS512 は 64 octets 以上(5.2.2-3、OIDC 16.19)――
client assertion の aud――issuer の文字列のみ(5.3.2.1-8)
DPoP proof の署名アルゴリズム――refresh token でも 5.4.1 のアルゴリズムに制限
修正前修正後
認可コードのトークンリクエスト検証検証
PARFAPI 2.0 の署名アルゴリズム・鍵長・aud のみすべて
refresh token検証なしすべて
満たさないときのエラー400(認証方式の制限は unauthorized_client、ほかは invalid_client)invalid_client(RFC 6749 5.2)。トークンエンドポイント・PAR とも、ほかのクライアント認証の失敗と同じ 401(§17)

あわせて次を変更した。

  • client_assertion_type:private_key_jwt / client_secret_jwt で、値が urn:ietf:params:oauth:client-assertion-type:jwt-bearer(RFC 7523 2.2)でなければ invalid_client(トークンエンドポイント・PAR とも 401)。従来はパラメータがあるかどうかしか見ていなかった。FAPI に限らない
  • RSA1_5:FAPI 1.0 Advanced(8.6.1)で、RSA1_5 で暗号化した Request Object を invalid_request_object、id_token_encrypted_response_alg / authorization_encrypted_response_alg が RSA1_5 のクライアントを unauthorized_client で拒否する
  • client_id_alias:FAPI 1.0 Baseline の client identifier の不一致(5.2.2-19)の確認が、assertion の iss / sub をクライアント ID とだけ比べていたため、client_id_alias で認証するクライアントを拒否していた。この確認を削除し、汎用の検証(iss が client_id パラメータと一致、sub が iss と一致)に任せた(Issue #1921)
  • x-fapi-interaction-id:UserInfo がリクエストの値(無ければ UUID)を返す(FAPI 1.0 Baseline 6.2.1-11)。リクエストで受け取った値は、アプリケーション API のログの MDC に fapi_interaction_id として出す(6.2.1-12)

7.1 影響を受ける構成​

FAPI のスコープで PAR または refresh token を使うクライアントのうち、認可コードのトークンリクエストでは通らない認証をしているもの。認可コードのトークンリクエストで同じ要件をすでに満たしているクライアントは影響しない。

refresh token のリクエストのプロファイルは、その時点のテナントの fapi_baseline_scopes / fapi_advance_scopes / fapi20_scopes で決まる。これらの設定を変えると、発行済みの refresh token にも変更後のプロファイルの要件がかかる。

RSA1_5 を設定しているクライアントは次で確認できる。

-- PostgreSQL
SELECT tenant_id, id FROM client_configuration
WHERE payload->>'id_token_encrypted_response_alg' = 'RSA1_5'
OR payload->>'authorization_encrypted_response_alg' = 'RSA1_5';

-- MySQL
SELECT tenant_id, id FROM client_configuration
WHERE JSON_UNQUOTE(JSON_EXTRACT(payload, '$.id_token_encrypted_response_alg')) = 'RSA1_5'
OR JSON_UNQUOTE(JSON_EXTRACT(payload, '$.authorization_encrypted_response_alg')) = 'RSA1_5';

7.2 確認​

ケーステスト
FAPI 1.0 Baseline:refresh token で鍵長の要件、client_id_alias での認可コード・refresh token、UserInfo の x-fapi-interaction-ide2e/src/tests/spec/fapi_baseline.test.js
FAPI 1.0 Advanced:PAR で RS256 の client assertion を拒否e2e/src/tests/spec/fapi_advance.test.js
FAPI 1.0 Advanced:RSA1_5 の拒否FapiAdvanceVerifierRsa15Test(ユニットテスト)
FAPI 2.0:refresh token で aud が issuer 以外の client assertion を拒否e2e/src/tests/spec/fapi2_0_security_profile.test.js
client_assertion_type の値e2e/src/tests/spec/rfc7523_jwt_client_authentication.test.js
5.2.2-19 の不一致の検出(汎用の検証)e2e/src/tests/spec/fapi_baseline.test.js / fapi_ciba.test.js

8. 認可拒否のエラー応答のキー​

🟡 挙動変更。 POST /{tenant-id}/v1/authorizations/{id}/deny がエラー(400 / 500)を返すとき、説明のキーが errorDescription になっていた。ほかの API と同じ error_description に直した(Issue #1919)。成功時の応答(redirect_uri)とリダイレクトで返すエラーは変わらない。

エラーになるのは、クライアント設定・サーバー設定が見つからない場合とサーバーエラーで、通常の認可拒否では起きない。


9. 認可画面を別サイトに置く構成(Issue #1904 / PR #1905)​

🟢 追加。 認可画面を idp-server と別サイト(別の登録可能ドメイン)に置くと、認可画面からの XHR がサードパーティになり、Safari ではログインも OP セッションの保存も成立しなかった。別サイト認可画面モードを追加し、ブラウザの識別を Cookie から使い捨ての値 auth_proof に移し、認可フローの最後に idp-server を /complete でトップレベル遷移させるようにした。

9.1 影響​

  • 既定は無効。 テナントの ui_config.cross_site またはクライアントの extension.cross_site_authorization_view を true にしない限り、挙動は変わらない
  • RP の実装は変わらない。 RP に届くコールバックは従来どおり
  • 自作の認可画面は対応が要る(有効にする場合のみ)。応答の auth_proof の保持、authorize への付与、/complete への遷移。手順は 認可画面を別サイトに置く
  • DB マイグレーションは無い。 状態は既存の authentication_transaction.attributes に置く。トランザクションの更新で attributes も書くようになった

9.2 有効にする前に確認すること​

項目必要なこと
認可画面URL の fragment の view_binding を x-view-binding ヘッダーで送ることと、auth_proof に対応している(対応していない画面で有効にすると、認可画面からの呼び出しが 401、authorize が 400)
session_config.cookie_same_siteLax(既定)または None。Strict では /complete が拒否される
認証ポリシーの auth_session_binding_requiredtrue(既定)にする。別サイト構成を動かすために false にしていた場合は、有効化のあとで戻す。false のままだと view_binding と /complete でのブラウザの束縛が外れる
cors_config.allow_origins認可画面のオリジンを含む
idp-server の前段(API Gateway / CDN / WAF)x-view-binding ヘッダーを転送する。CORS のプリフライトに前段が応答する構成では、Access-Control-Allow-Headers にも含める(idp-server 側は設定不要で含める)
切り替えの方法使用中のクライアントで切り替えると、認証途中の利用者が一度だけ失敗する。別クライアントへの乗り換えかメンテナンスで切り替える

10. リクエストオブジェクトの独自クレーム(Issue #1938 / PR #1937)​

リクエストオブジェクト(JAR)を使った認可リクエストで、カスタムパラメータが署名されていないクエリからだけ取られ、JWT の独自クレームは捨てられていた。RFC 9101 Section 4 のとおり、リクエストオブジェクトからも読むようにした。

リクエストの形修正前修正後
request= JWTクエリだけJWT + クエリ。同じキーは JWT(OIDC Core 6.3.3)
request= JWT(FAPI 1.0 Advanced)クエリだけJWT だけ(FAPI 1.0 Advanced 5.2.2-10)
PAR本文(本文の JWT の独自クレームは捨てていた)本文 + JWT
  • JWT の登録済みクレーム(iss / sub / aud / exp / nbf / iat / jti)と、OAuth / OIDC の既知パラメータは除く。数値と真偽値は文字列にし、オブジェクトと配列は取り込まない
  • 🔴 FAPI 1.0 Advanced:クエリだけで渡したカスタムパラメータは使われない。画面の振り分け(ui_config.variant_param、既定 view_version)をクエリで渡しているクライアントは、リクエストオブジェクトに入れる
  • 🟡 リダイレクト URL:リクエストオブジェクトの値は、認可画面へのリダイレクト URL に載せない(署名・暗号化して送られた値を、ブラウザの履歴や Referer、画面側のアクセスログに出さないため)。リクエストオブジェクトを含む PAR は、どの値がリクエストオブジェクトから来たか区別できないので、カスタムパラメータを URL に載せない。view-data の custom_params には、どの場合も載る。URL のクエリから読んでいる認可画面は、view-data から読むようにする
  • 画面の振り分けの判定はサーバー側で全部の値を見るので、リクエストオブジェクトに入れた値でも振り分けは効く

11. 認可リクエストのカスタムパラメータとの照合(Issue #1907)​

🟢 追加。 RP が認可リクエストで渡した値(例:会員番号)と、ログインした利用者の属性を、認証ポリシーの条件式や属性照合の条件のステップで突き合わせられるようにした。

  • 条件式から $.request.custom_params.<名前> で読める。右辺は value_path でコンテキストの path を指定できる(eq / ne のみ。どちらかの値が無いとき eq は false)
  • 見えるのは、ポリシーの custom_params_trusted_sources に含まれる出どころの値だけ。既定は pushed(クライアント認証をした PAR)と request_object(署名されたリクエストオブジェクト)。クエリ、署名の無いリクエストオブジェクト、公開クライアントの PAR の値は query 扱いで、既定では見えない
  • 出どころは認可エンドポイントで決め、認証トランザクションの context.custom_params に保存する(既存の JSON 列。マイグレーションは無い)
  • 属性照合のステップ、または $.request.* / $.attribute-verification.* を見る条件を含むポリシーでは SSO しない。view-data の session_enabled が false、authorize-with-session は 400、prompt=none は login_required。照合は認可リクエストごとの確認で、セッションでは代わりにならないため(属性照合は v0.14.0 で追加したもので、既存の構成への影響は無い)

既存の設定には影響しない(新しい項目を書かない限り挙動は変わらない)。詳細は 認証ポリシー。


12. DPoP proof と Client Attestation PoP JWT のリプレイ検出(Issue #1893)​

DPoP proof(RFC 9449 Section 11.1)と attest_jwt_client_auth の Client Attestation PoP JWT(draft-ietf-oauth-attestation-based-client-auth Section 12.1)で、受け付けた jti を記録し、同じ JWT の 2 回目を拒否するようにした。

attest_jwt_client_auth は v0.14.0 で追加した機能(§22)のため、この節の Client Attestation PoP JWT に関する記述は、v0.13.0 からの更新には影響しない。

  • 🟡 iat の許容範囲を ±60 秒に短縮(従来は ±5 分)。テナントの authorization_server.extension で 1〜600 秒の範囲で変更できる(範囲外はテナントの作成・onboarding・認可サーバー設定の更新で 400)

    設定既定
    dpop_proof_acceptable_window_seconds60
    client_attestation_pop_acceptable_window_seconds60
  • 🟡 同じ proof の 2 回目を拒否。対象はトークンエンドポイント(各グラント)、PAR、userinfo、introspection、/me 系 API の DPoP 束縛の確認、ABCA のクライアント認証(トークン、PAR、CIBA、取り消し、introspection)。リクエストごとに新しい proof を作っているクライアントには影響しない

  • 記録は Redis に置く(SET NX EX、iat の窓が閉じるまで)。キーはテナント・種類・鍵(DPoP は JWK Thumbprint、ABCA は client_id)・jti のハッシュ。DB のマイグレーションは無い

  • Redis を設定していない配備(起動時に warn)と Redis のエラー時は記録せず、iat の窓だけで判定する

  • 🟡 client assertion(private_key_jwt / client_secret_jwt)も同じ仕組みで 2 回目を拒否する(Issue #1902)。RFC 7523 Section 3 の要件 7("The authorization server MAY ensure that JWTs are not replayed by maintaining the set of used "jti" values ...")。対象はクライアント認証をするすべてのエンドポイント(トークン、PAR、CIBA、取り消し、introspection)。記録はテナント・client_id・jti のハッシュをキーに、exp と下記の最長期間の早いほうまで残す。署名とほかの検査をすべて通った assertion だけが jti を使う。リクエストごとに新しい assertion を作っているクライアントには影響しない

  • 🔴 client assertion を受け入れる期間を最長 60 秒に制限する(Issue #1902)。RFC 7523 Section 3 の要件 4("the authorization server may reject JWTs with an "exp" claim value that is unreasonably far in the future")。iat があれば iat から 60 秒より後は拒否(exp が先でもよい)、iat が無ければ exp が今から 60 秒より先のものを拒否。テナントの authorization_server.extension.client_assertion_max_lifetime_seconds(1〜600 秒、範囲外は 400)で変更できる。従来は exp が先であれば何時間でも受け入れていた


13. 同意画面での要素の選択を verified_claims にも適用(Issue #1947)​

🟢 追加。 v0.13.0(#1816)の、同意画面で配列クレームの要素を選ぶ仕組み(/authorize の granted_claim_values)は claims:* のクレームにしか効かず、verified_claims のクレームは一部だけを選んでも全要素が出ていた。verified_claims.claims の配列にも効くようにした。

  • 選択は、トークン上の位置と同じ granted_claim_values.verified_claims.claims.<名前> に入れる。候補は view-data の claim_values.verified_claims.claims.<名前> に出る
  • 同じ名前の custom_properties のクレームとは別の選択(要素の形が違うことがあるため)。verification は選択の対象外
  • verified_claims を出すすべての経路に効く(verified_claims:<名前> スコープのアクセストークン・UserInfo、claims パラメータの ID Token・UserInfo)
  • 選択はグラントに保存する既存の値に入る。DB のマイグレーションは無い
  • 🟡 view-data の claim_values は、認証が成功したあとにだけ返す。 従来(v0.13.0 の custom_properties の候補)は、login_hint や多段認証の 1 段目でユーザーが決まった時点で返していた。候補は同意画面のためのもので、本人であることが確かめられる前に出す必要はない

既存の設定には影響しない(選択を送らない限り挙動は変わらない)。

ローリングデプロイの途中

v0.14.0 のノードで同意して保存されたグラントでも、v0.13.0 のノードがトークンや UserInfo を返すと、verified_claims の選択を読まないため全要素が出る(壊れはしない。custom_properties の選択はどちらのノードでも効く)。verified_claims の要素の選択が確実に効くのは、全ノードの入れ替えが終わってから。


14. binding message の照合のバリデーション強化(Issue #1950)​

🟡 挙動変更。 authentication-device-binding-message は、トランザクションの binding message とリクエストの binding_message を比べる。照合する値をトランザクションが持たないとき(binding_message を付けない CIBA リクエスト、認可コードフロー)は、400 invalid_request(Binding Message is not set for this authentication transaction)にした。従来は、空の binding_message を送ると一致して成功していた。

  • binding_message を付けた CIBA リクエストは、従来どおり一致すれば成功する
  • binding_message を付けない CIBA リクエストで、このステップを認証ポリシー(success_conditions など)に入れている構成では、このステップは通らなくなる。CIBA リクエストに binding_message を付けるか、ポリシーからこのステップを外す

15. 監査ログの API の応答で秘密の値を隠す(Issue #1955)​

🟡 挙動変更。 監査ログは、操作のリクエストと操作の前後のリソースを記録したまま保存しているため、クライアントシークレットやユーザー作成時のパスワード、認可サーバーの秘密鍵などが含まれることがある。監査ログの管理 API(システム・組織の一覧・詳細)の応答で、これらの値を [SCRUBBED] に置き換えるようにした。

  • 対象は request / before / after / user_payload / attributes の中の、パスワード・シークレット・トークン・鍵などのキー。入れ子のオブジェクト・配列の中も含む
  • キーは丸ごと一致で比べる(大文字小文字は区別しない)。client_secret_expires_at や token_endpoint のように、秘密そのものではない項目は隠さない
  • 記録されている内容は変わらない(応答だけを置き換える)。DB のマイグレーションは無い
  • 隠すキーは固定。テナントごとに足す設定は無い

16. client attestation の x5c で途中の CA をトラストアンカーにできる(Issue #1957)​

🟢 追加。 v0.14.0 で追加した attest_jwt_client_auth(§22)の、client_attestation_trust_source: x5c での証明書チェーンの検証の仕様。v0.13.0 からの更新には影響しない。

  • 検証は RFC 5280 のパス検証(Java 標準の PKIX 実装)で行う。client_attestation_trusted_root_certificates に設定した証明書はトラストアンカー(subject と公開鍵)として扱い、パスはそれが発行した証明書から始まる
  • トラストアンカーには、ルートだけでなく途中の CA(Attester の証明書を発行する CA など)も置ける。x5c がその CA や、さらに上のルートを含んでいても通る
  • トラストアンカーを、同じ subject と鍵で更新した証明書に差し替えても通る。トラストアンカーより上の証明書は読まない
  • x5c の先頭(リーフ)は CA でない証明書に限る。自己署名のリーフは拒否する
  • 各証明書の issuer が上の証明書の subject と一致すること(RFC 5280 6.1.3)を検査する
  • JDK の jdk.certpath.disabledAlgorithms に該当する署名アルゴリズム・鍵長(MD5、短い RSA 鍵など)の証明書を含むチェーンは拒否する
  • 証明書チェーンは 10 枚まで。Android Key Attestation と Apple App Attest の証明書チェーンにも効く(実際のチェーンは数枚)

Apple App Attest(registered_instance_key のインスタンス登録)も同じ部品で検証する。実際に Apple が発行したチェーン(credCert と Apple App Attestation CA 1)で確認している。

確認すること:

  • ルートが Attester 以外の用途の証明書も発行しているなら、client_attestation_trusted_root_certificates をその下の Attester 専用の発行 CA に替えると、ほかの用途の証明書の鍵で Client Attestation JWT を作れなくなる

17. クライアント認証の失敗を RFC どおりに返す(Issue #1891)​

🔴 破壊的(一部)。 クライアント認証の失敗(invalid_client)を、トークン・revocation・introspection・PAR・CIBA のどこでも 401 で返す。400 になるのは attest_jwt_client_auth の use_attestation_challenge だけ(draft-ietf-oauth-attestation-based-client-auth-11 §6.1)。仕様の整理は クライアント認証に失敗したときの応答。

従来変更後根拠
Introspection の認証失敗400 + {"active": false, "error": "invalid_client", "status_code": 400}401 + {"error": "invalid_client"}RFC 7662 §2.3
PAR の認証失敗400401RFC 6749 §5.2(401 は MAY)。トークンエンドポイントと揃える
client_secret_basic で失敗WWW-Authenticate なしWWW-Authenticate: Basic realm="<issuer>"RFC 6749 §5.2(MUST)、RFC 7617 §2
別の方式で登録したクライアントが Basic ヘッダーも送る登録した方式で認証(Basic は無視)401 + WWW-AuthenticateRFC 6749 §2.3(1 リクエストに 1 方式)
存在しない client_idエンドポイントごとに 400 / 401、PAR は invalid_request401 invalid_clientRFC 6749 §5.2、CIBA Core §13

Introspection の変更で応答が変わるのは、Resource Server 自身の資格情報が無効なときだけ。そうした Resource Server はどのトークンも検証できず、もともと動いていない。認証に成功している Resource Server が受け取る応答(200 + active)は変わらない。

確認すること:

  • client_secret_post / private_key_jwt などで登録したクライアントが、Authorization: Basic ヘッダーも送っていないか。HTTP クライアントや SDK の設定で両方を送るものがある
  • Introspection のエラー判定に active だけを使っている Resource Server は、401 を「資格情報の不備」として扱えるか

18. 身元確認の process を呼べる入口を分ける(Issue #1966)​

🔴 破壊的(一部)。 エンドユーザー向けの申込み API(/v1/me/identity-verification/applications/...)とコールバック API(/internal/v1/identity-verification/callback/...)は、同じ processes から process を引く。process に caller を書くと、呼べる入口を 1 つに限定できる。

callerエンドユーザー向けコールバック
end_user実行できる404
external_service404実行できる
省略・request.basic_auth あり404実行できる
省略・request.basic_auth なし実行できる実行できる(従来どおり)
  • 書いていない設定で挙動が変わるのは、request.basic_auth のある process をエンドユーザー向けの入口から呼ぶ場合だけ。basic_auth を確認するのはコールバック側だけなので、コールバック用とみなす
  • 同梱のテンプレート(ekyc / financial-grade / financial-grade-2.0)と config/examples/ekyc の evaluate-result、ekyc の ongoing の callback-result を caller: external_service にした。helpers.sh の iv_evaluate はコールバックから呼ぶ(iv_callback を追加)
  • 身元確認設定の登録・更新で、caller に end_user / external_service 以外を書くか、end_user の process に request.basic_auth(username と password の両方)を書くと 400 になる
  • 登録済みの設定は自動では変わらない。同梱のテンプレートから作った設定は、evaluate-result などに caller: external_service を書くか、テンプレートから作り直す
  • ongoing テンプレートの callback-result は、dependencies と process_sequence で apply の後にだけ実行できる形にした。再送は受け付ける(承認・却下などのあとに再送しても、ステータスと承認時の処理は繰り返さない)
  • コールバック API はインターネットに公開しない前提。request.basic_auth は追加の防御

確認すること:

  • 承認(transition.approved)や verified_claims の元になる値を $.request_body から読む process に、caller: external_service を書いたか
  • basic_auth を設定した process を、エンドユーザー向けの入口から呼んでいる画面・自動化がないか
  • コールバック API(/{tenant-id}/internal/...)がインターネットから届かないか

19. 申込みの種別をパスと照合する(Issue #1969)​

🔴 破壊的。 申込みを ID で扱う次の API で、パスの {verification-type} が申込みの種別と違えば、申込みが見つからないときと同じ 404(invalid_request)にする。従来は申込みの種別を見ずに、パスの種別の設定で処理していた。

  • POST /{tenant-id}/v1/me/identity-verification/applications/{type}/{id}/{process}
  • DELETE /{tenant-id}/v1/me/identity-verification/applications/{type}/{id}
  • POST /{tenant-id}/internal/v1/identity-verification/callback/{type}/{id}/{process}
  • POST /{tenant-id}/internal/v1/identity-verification/callback/{type}/{process}(ボディの値で申込みを特定する形)

あわせて、同梱のテンプレート・設定例から、サーバーが読んでいなかったキー(トップレベルの external_service / external_application_id_param、common.external_service)を外した。申込みをボディの値で特定するキーは common.callback_application_id_param。直接登録の設定の説明(verified_claims_configuration)も、実際に読まれる result.verified_claims_mapping_rules に直した。申込み一覧 API の検索条件 external_service は効いていなかったので、API 仕様から外した。

確認すること:

  • 申込みと違う種別のパスで、これらの API を呼んでいないか
  • 自分で作った設定で、external_application_id_param をトップレベルに書いて申込みの特定に使うつもりになっていないか(使われるのは common.callback_application_id_param)

20. マッピングと身元確認の process_sequence の堅牢化(Issue #1971)​

🔴 破壊的(一部)。

マッピング(body_mapping_rules などすべて):

  • to: "*" で展開したキーは、そのままのキーとして入れる。従来は "a.b" を {"a": {"b": ...}} に、数字だけの要素を配列の添字として組み立てていた。to に書いたパス(claims.name、verification.evidence.0.type など)の扱いは変わらない
  • to のパスの配列の添字は 0〜999。超えると、そのルールが動いたときに設定の誤りとしてエラーにする。設定の保存時には確かめない
    • 多くの入口では 500 server_error(The server has a configuration error. Please contact the administrator.)
    • 認証や連絡先の確認でコードを送るとき、送信を http_request で行う設定の、送信用のマッピングでは、送信の失敗として 400
    • 応答を返さない処理(セキュリティイベントのフック、ユーザー削除時の FIDO-UAF のデータ削除、連絡先変更のお知らせ)では、失敗として記録するかログに残す

身元確認の process_sequence:

  • 断られた呼び出し(required_processes が未完了、allow_during_statuses の対象外、allow_retry: false の再実行)は、process の実行として記録しない。従来は /v1/me/... の申込み ID 付きの process で記録され、前提を満たしたあとも再実行として 400 になっていた。申込み ID 付きの process で、ほかの pre_hook や execution で失敗した呼び出しは、従来どおり failure_count に数える(コールバックは従来どおり失敗を記録しない)
  • allow_during_statuses のある process を申込み ID なしで呼ぶと、400 pre_hook_validation_failed(従来は 500)
  • allow_during_statuses の値は、申込みのステータスの小文字(requested / applying / applied / examination_processing / approved / rejected / expired / cancelled)。それ以外を書いた設定は登録・更新で 400。従来は大文字で書くと一致せず、常に拒否されていた
  • 大文字などの値で保存済みの設定は、そのまま読み込んで動く(従来どおり常に拒否し、断られた呼び出しは記録しない)。更新は設定全体の置き換えなので、値を直すまで 400 になる

確認すること:

  • "*" で展開する元のデータ(リクエストや外部 API の応答)に . を含むキーがあり、入れ子になる動きを使っていないか
  • allow_during_statuses を書いた設定の値が小文字か

21. 認証の処理がユーザーを信頼する範囲の見直し(Issue #1973)​

認証トランザクションのユーザーを、どこまで信頼して処理を進めるかを見直した(PR #1974 / #1975 / #1976 / #1977)。

21.1 ユーザーに書き込む操作は認証済みのユーザーに限る​

🔴 破壊的変更。 次の操作は、トランザクションにユーザーがいるだけでなく、ユーザーが認証済みであることを求める。

  • FIDO2 / FIDO-UAF のデバイスの登録(チャレンジと登録)と削除
  • SMS / Email の 2 要素目で allow_registration: true のとき、電話番号・メールアドレスを持たないユーザーに、リクエストの値を登録する処理

「認証済み」として扱うのは次のどれか。

  • 同じ認可リクエストで、認証(パスワード、Email、SMS、新規登録、外部 IdP など)が成功している
  • CIBA のリクエスト
  • /me の API(アクセストークンのユーザー)

login_hint でユーザーを指定しただけでは認証済みにならない。ログイン済み(SSO)のユーザーでも、新しい認可リクエストに login_hint を付けてデバイス登録へ進む場合は、そのリクエストの中で認証してから登録する。満たさないときは次のエラーになる。

操作エラー
FIDO2 / FIDO-UAF の登録・削除400 unauthorized
2 要素目での連絡先の登録チャレンジが 400 unauthorized

電話番号・メールアドレスをすでに持っているユーザーの 2 要素目は、これまでどおり login_hint だけでもチャレンジできる(コードは登録済みの値に送る)。

次の流れは、これまでどおり登録できる(登録が認証として数えられなくなる影響は 21.2)。

  • 新規登録(initial-registration)のあと、そのままデバイスを登録する
  • パスワードなどで認証したあと、デバイスや連絡先を登録する
  • /me/mfa/... からの登録・削除
  • CIBA でのデバイス登録(ユーザーは最初から認証済みとして扱う)

🟡 挙動変更。 外部 API 認証、パスワード、SMS / Email のチャレンジ・検証で $.user を外部に渡すかどうかも、同じ「認証済み」の判定で決めている。判定が上のとおり変わったので、/me の API では最初の interaction から $.user が渡るようになった(これまでは CIBA だけ)。

21.2 デバイスの登録は認証として数えない​

🔴 破壊的変更。 FIDO2 / FIDO-UAF の登録と、FIDO2 の削除の成功を、認証の成功として数えないようにした。これまでは、登録の成功が認証の成功として扱われていた。

修正前修正後
amr登録だけで fido2 / fido-uaf が入る入らない
auth_time登録の時刻になる認証の時刻だけ
amr から決まる acr(acr_mapping_rules)、level_of_authentication_scopes登録だけで満たす満たさない

認証ポリシーの成功条件に登録の成功($.fido2-registration.success_count など)を書いている場合、その条件はこれまでどおり評価される。認証の成功として数えないだけ。

成功条件(success_conditions)を書いていないポリシーでは、どれかの認証が成功するとトランザクションが成功する。登録・削除の成功はこれに数えないため、登録・削除だけではトランザクションが成功しない。このとき interaction の応答は成功になる。

  • 登録:ユーザーのデバイス一覧に追加されない(FIDO2 の WebAuthn4j の credential や、FIDO-UAF の FIDO サーバー側の登録は済んでいる)
  • FIDO2 の削除:credential の削除も応答もこれまでどおり。トランザクションが完了しないまま有効期限まで残る

CIBA でも、登録だけで終える構成は amr / acr が変わり、成功条件を書いていなければ完了しない。/me 用の flow(fido2-registration / fido-uaf-registration / fido2-deregistration)のポリシーには、登録・削除の成功を成功条件に書く。

21.3 認証として数えるが、本人を確かめないことがある interaction​

🔒 要点検。 次の成功も認証として数える。これらで本人を確かめていない構成は、device_registration_conditions で登録の条件を絞る。

  • 外部 API 認証(external-api-authentication)。外部の処理を始めるだけの interaction も含む
  • ナンバーマッチング(authentication-device-number-matching)。転記されたコードと、device_id(紐づいた認証デバイスか)を照合するもので、デバイスの持ち主であることの確認(鍵による所持の確認や生体認証)はしない

device_registration_conditions を見るのは、FIDO2 の登録と、FIDO-UAF の登録・削除。FIDO2 の削除と、2 要素目での連絡先の登録には効かない。

21.4 登録解除で持ち主を照合する​

🟡 挙動変更。

  • FIDO2:credential の持ち主と、トランザクションのユーザーを照合する。照合に使う username はユーザーから決め(テナントの identity_unique_key_type による)、リクエストの username は使わない。持ち主でない credential は、存在しないときと同じ 400(credential_not_found)。credential_id が無いときは 500 でなく 400(invalid_request)
    • この 2 つのエラーが応答の本文に出るのは、response.body_mapping_rules で $.error を写す設定のとき(標準のテンプレートは写さない)
    • ユーザーから username を決められない(identity_unique_key_type が EMAIL でメールアドレスが無いなど)ときは 400 forbidden
    • 照合するのは idp-server 内蔵の WebAuthn4j(webauthn4j_deregistration)だけ。登録解除を http_request で外部に渡す設定では照合しない。$.request_body を外部に写す設定では、username がユーザーから決めた値になる
    • 照合は登録時に保存した username と比べる。登録後にユーザーの値(identity_unique_key_type が EMAIL ならメールアドレスなど)が変わった場合、古い credential は削除できない
  • FIDO-UAF:device_id がトランザクションのユーザーのデバイスか、FIDO サーバーに送る前に確かめる。違うとき・device_id が無いときは、FIDO サーバーに送らず 400 invalid_request。FIDO サーバーに送る device_id は、確かめた値で上書きする

21.5 認証の post_hook での照合​

🟢 追加 / 🔒 要点検。 認証設定の post_hook に verifications を追加した。外部サービスでの実行のあとに、idp-server で 2 つの JSONPath の値を照合する(type: match)。評価するのは FIDO-UAF の認証(fido-uaf-authentication)だけで、ほかの interaction に書いても使われない。照合するのは response.body_mapping_rules を通す前の実行結果。照合に失敗したときは 400 invalid_request。値が無い・設定が不正なときも失敗として扱う。

FIDO-UAF の認証で、FIDO サーバーが認証したデバイスの ID を返す場合は、それとチャレンジの device_id($.interaction.device_id)を照合する設定を推奨する。設定しないテナントの動きは変わらない。設定例は FIDO-UAF。

21.6 確認すること​

  • login_hint だけで決めたユーザーで、デバイスの登録・削除や、2 要素目での連絡先の登録をさせていないか
  • 認証のあとにデバイスを登録して認可を終えるとき(CIBA では登録だけのときも)、amr / acr に fido2 / fido-uaf が入ることを前提にしていないか
  • 外部 API 認証やナンバーマッチングの成功を認証として使う構成で、device_registration_conditions を設定しているか
  • FIDO-UAF の認証で、post_hook の照合を設定できるか
  • /me 用の登録・削除のポリシーに、成功条件(success_conditions)を書いているか
  • FIDO2 の登録解除を http_request で外部に渡している場合、外部で持ち主を照合しているか($.request_body を写す設定では、username はユーザーから決めた値になる)

E2E 用のテストテナント(config/examples/e2e/test-tenant/authentication-policy/oauth.json)からは、$.fido2-registration.success_count を成功条件にする行を削除した。

認可コードフロー・CIBA の認証ポリシーで、登録の成功を条件に書いているもの(登録で認可を終えられ、amr / acr が変わる候補)は、次で洗い出せる(/me 用の fido2-registration などの flow は対象外)。payload 全体を文字列で探すので、成功条件以外に書いた参照や無効にしたポリシーも拾う。

-- PostgreSQL
SELECT tenant_id, id, flow FROM authentication_policy
WHERE flow IN ('oauth', 'ciba') AND (payload::text LIKE '%fido2-registration.success_count%'
OR payload::text LIKE '%fido-uaf-registration.success_count%');

-- MySQL
SELECT tenant_id, id, flow FROM authentication_policy
WHERE flow IN ('oauth', 'ciba') AND (CAST(payload AS CHAR) LIKE '%fido2-registration.success_count%'
OR CAST(payload AS CHAR) LIKE '%fido-uaf-registration.success_count%');

22. Attestation-Based Client Authentication(Issue #1521 / PR #1753)​

🟢 追加。 クライアント認証の方式に attest_jwt_client_auth(draft-ietf-oauth-attestation-based-client-auth)を追加した。あわせて、端末ごとの鍵を登録する Client Instance と、その管理 API を追加した。

設定しないテナント・クライアントの動きは変わらない。

  • ディスカバリーの client_attestation_signing_alg_values_supported / client_attestation_pop_signing_alg_values_supported / challenge_endpoint は、設定したときだけ返す
  • 既存のクライアント認証の方式は変わらない
  • refresh token をクライアントの鍵に結びつけるのは、attest_jwt_client_auth で認証したときだけ

詳細は Attestation-Based Client Authentication。

22.1 DBマイグレーション​

⚙️ 全環境で必須。

ファイル内容
V0_14_0_2__client_instanceclient_instance テーブル
V0_14_0_3__client_instance_registrationclient_instance_registration_challenge テーブル
V0_14_0_4__client_instance_bound_refresh_tokenoauth_token に client_instance_thumbprint / client_instance_id を追加(NULL 許容)

PostgreSQL は libs/idp-server-database/postgresql/、MySQL は libs/idp-server-database/mysql/(ファイル名の末尾が .mysql.sql)。PostgreSQL の新規テーブルには RLS を設定している。

新規テーブルと NULL 許容の列の追加だけで、既存の列とデータは変えない。

22.2 管理権限・セキュリティイベント・期限切れデータの削除​

  • 管理権限:idp:client-instance:create / read / revoke / delete を新設した。権限を明示列挙しているロールで Client Instance の管理 API を使うときは追加する
  • セキュリティイベント:client_instance_registration_challenge_issued / client_instance_registration_success / client_instance_registration_failure / client_instance_revoked を追加した
  • 期限切れデータの削除:POST /v1/admin/operations/delete-expired-data が client_instance_registration_challenge も削除し、レスポンスの内訳にこのキーが増えた

22.3 payload を読めない JWT への応答​

🟡 挙動変更。 attest_jwt_client_auth を設定しないテナントにも効く。payload を claims として読めない JWT は、これまで 500 になっていた。入口ごとのエラーにした。

入口修正後
DPoP proofinvalid_dpop_proof
JWT Bearer Grant の assertioninvalid_grant(従来は server_error)
client_assertionクライアント認証の失敗
それ以外400 invalid_request

23. 認証のたびに OP セッションの認証情報を更新する(Issue #1912 / PR #1945)​

🟡 挙動変更。 認可エンドポイントでの認証(ブラウザの認可フロー)で、同じユーザーの有効な OP セッションがあるときに認証が成功すると、OP セッションの auth_time / acr / amr と、認証の結果(SSO のときに成功条件の判定に使う)を今回の認証の値に置き換える。これまでは最終アクセス時刻だけを更新し、最初に認証したときの値が残っていた。CIBA は対象外。

修正前修正後
prompt=login で再認証したあとの SSO の ID Token最初の認証の auth_time / acr / amr再認証の値
max_age を超えて再認証したあと、同じ max_age のリクエストSSO できず、毎回ログインを求めるmax_age 以内なら SSO できる
ステップアップ(MFA など)のあと、高い acr を求めるリクエストSSO できないSSO できる

acr が下がる認証でも置き換える。強い認証のあとに弱い認証をさせると、その後に高い acr を求めるリクエストでは、強い認証をやり直すことになる。

あわせて、auth_time の時刻の変換を TIME_ZONE に合わせた。これまでは UTC として扱っていたため、TIME_ZONE が UTC 以外の環境では、SSO の max_age の判定が時差の分ずれていた(UTC より東ならゆるく、西なら厳しく。JST では 9 時間ゆるかった)。

この修正で、TIME_ZONE が UTC 以外の環境では次のことが起きる。

  • 更新前に作られた OP セッションから SSO すると、ID Token の auth_time が時差の分ずれる(JST なら 9 時間先)。max_age の判定も、そのセッションでは従来のずれが残る
  • ローリングデプロイの途中、新しいノードが作った OP セッションを古いノードが読むと、ID Token の auth_time が逆向き(過去)にずれる。max_age の判定は正しく行われる。古いノードが作った OP セッションは、上と同じ扱いになる

確認すること:

  • SSO で受け取る ID Token の auth_time / acr / amr を、最初の認証の値として扱っていないか
  • 強い認証のあとに、同じセッションで弱い認証をさせる流れがないか
  • TIME_ZONE を UTC 以外にしている環境で、max_age が従来の判定を前提にしていないか
  • TIME_ZONE を UTC 以外にしている環境では、更新後に既存の OP セッションを失効させるか、有効期限が切れるまで待つ

24. authentication-cancel を cookie で照合しない(Issue #1940 / PR #1942)​

🟡 挙動変更。 認証端末から送る POST /{tenant-id}/v1/authentications/{id}/authentication-cancel を、AUTH_SESSION(ブラウザの cookie)の照合にかけないようにした。authentication-device-deny / fido-uaf-cancel と同じ扱いになる。

これまでは、認証ポリシーの auth_session_binding_required が true(既定)のとき、cookie を持たない端末からの取り消しは 401 auth_session_mismatch になっていた。修正後は 200 で取り消せて、認証トランザクションは失敗になる。

認可画面から送る POST /{tenant-id}/v1/authorizations/{id}/authentication-cancel も、同じく cookie で照合しない。どちらも、できるのは認証トランザクションを失敗させることだけ。


25. /me/mfa で oauth / ciba を指定できない(PR #1875)​

🟡 挙動変更。 POST /{tenant-id}/v1/me/mfa/{mfa-operation-type} で oauth / ciba を指定すると、400 invalid_request を返す('oauth' is not an MFA operation. use its own endpoint.)。認可コードフローと CIBA の認証は、それぞれのエンドポイントで行う。

これまでは、その flow の認証ポリシーがあればトランザクションを作れたが、認可リクエストに紐づかないため、その後の操作が 500 になり使えなかった。ほかの flow(fido2-registration など、テナントが独自に付けた flow を含む)は変わらない。


26. 外部に渡す $.user に external_user_id を追加(Issue #1930 / PR #1931)​

🟡 挙動変更。 外部 API 認証、パスワード、SMS / Email、連絡先の確認で外部に渡す $.user に、ユーザーが external_user_id を持つときはその値を入れるようにした。

項目を指定して写すマッピング(from: "$.user.sub" など)では、外部に送る内容は変わらない。$.user を丸ごと写す設定では、外部に送る内容に external_user_id が増える。


27. MySQL の V0_10_0_3 / V0_10_0_4 を MySQL 8.0 で実行できる書き方に修正(PR #1753)​

⚙️ MySQL のみ。 V0_10_0_3__identity_verification_external_application_id_index.mysql.sql と V0_10_0_4__idp_user_tenant_created_at_index.mysql.sql は CREATE INDEX IF NOT EXISTS を使っていて、MySQL 8.0 では構文エラーになっていた。information_schema で索引の有無を確かめてから作る書き方に直した。

MySQL は DDL をトランザクションで実行できないため、失敗したマイグレーションは失敗として履歴に残り、次の migrate が止まる。migrate は最初の失敗で止まるので、記録されるのは V0_10_0_3 の失敗だけで、それ以降は適用されていない。

この状態の DB は、flyway info で状態を確かめてから、flyway repair で失敗の記録を消して migrate し直す。V0_10_0_3 から V0_14_0_4 までがまとめて適用される。PostgreSQL は変わらない。