代表的な面接トピック

バックエンド面接:トランザクション認可のためにOAuth RARとDPoPをどのように組み合わせるか?

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

決済APIは、ユーザーが正確な金額と送金先を指定した1回の送金を承認できるようにしつつ、盗難されたベアラートークンによるリプレイ認可を防ぐ必要があります。OAuth RARとDPoPをどのように組み合わせ、それぞれの保証をどこで強制しますか?

プロンプトと適用範囲

モバイルバンキングクライアントが決済APIを呼び出します。ユーザーは1人の受取人に対する500 USDの単一の送金を承認できます。認可サーバーは同意画面で取引の詳細を表示する必要があり、コピーされたアクセストークンが別のクライアントインスタンスから再利用されてはなりません。トークン発行、リソースサーバーでのチェック、リプレイ処理、鍵の紛失、検証を含む、Demonstrating Proof of Possession(DPoP)を伴うOAuth 2.0 Rich Authorization Request(RAR)の設計を説明してください。

これはバックエンドセキュリティおよびAPIコントラクトに関する質問です。具体的な通貨、金額、受取人は面接用の前提条件であり、市場の事実ではありません。

面接官がテストしていること

  • ユーザーが同意した内容とアクセストークンが呼び出せる権限を明確に区別できているか。
  • RARが構造化された認可詳細を保持し、DPoPがトークンを鍵にバインドしてリクエストごとに所持を証明することを理解しているか。
  • すべての強制ポイント(認可サーバー、トークンエンドポイント、リソースサーバー、決済台帳)が挙げられているか。
  • リプレイ、重複送信、鍵の紛失、プロバイダーからのあいまいな応答を適切に封じ込めることができるか。
  • ロールアウトとテストが、単にJWTの構文をチェックするだけでなく、ビジネスの不変条件を証明しているか。

「スコープに金額を入れてJWTに署名する」という回答は不十分です。優れた回答は、取引詳細を構造化して保持し、発行されたトークンをクライアントの鍵にバインドし、最終的な決済ステートマシンに決定権を持たせます。

最初に確認すべき明確化の質問

  1. 認可サーバーは決済台帳の所有者でもありますか?そうでない場合、台帳はコミット時に認可の決定を再確認する必要があります。
  2. 1回の認可は厳密に1回の送金に対して有効ですか、それとも制限された複数回の送金セットを認可できますか?RARの詳細とトークンの有効期限はこの境界に依存します。
  3. モバイルクライアントは再インストール後もハードウェア保護された鍵を維持できますか?また、紛失したデバイスはどのように失効されますか?
  4. リソースサーバーはDPoP nonce要件を受け取りますか?また、許容されるクロックスキューのウィンドウはどのくらいですか?
  5. 決済プロバイダーでのタイムアウト後は何が起こりますか:システムはステータスを照会できますか、それとも不明な状態を記録する必要がありますか?

30秒の回答フレームワーク

「広範なスコープを新設するのではなく、正確な送金内容をauthorization_detailsオブジェクトに格納します。クライアントは鍵ペアを作成し、トークンを要求する際にDPoPプルーフを送信します。認可サーバーはそのトークンをその公開鍵にバインドします。リソースサーバーはアクセストークン、DPoPプルーフ、HTTPメソッド、URI、タイムスタンプ、nonce、トークンと鍵のバインディングを検証し、認可されたトランザクション識別子を決済台帳に渡します。台帳はべき等キーを用いてこれを1回だけ受け入れ、終端状態を記録します。リプレイ保護、デバイス失効、プロバイダータイムアウト、監査証跡は個別の制御であり、それぞれの所有コンポーネントでテストされます。」

ステップバイステップの解決策

1. リクエストされたアクションのモデル化

RARは構造化されたauthorization_detailsリクエストを定義します。このオブジェクトには、登録されたtype、正確なアクション、金額、通貨、送金先識別子、およびトランザクション参照を含める必要があります。認可サーバーは、同意画面を提示する前に、スキーマ、アカウントの所有権、利用限度額、およびポリシーを検証します。台帳で使用されるのと同じ正規の値を表示する必要があり、クライアントから提供された表示ラベルだけでは不十分です。

json
{
  "type": "payment",
  "actions": ["transfer"],
  "amount": "500.00",
  "currency": "USD",
  "beneficiary_id": "b_123",
  "request_id": "rq_7f2"
}

認可結果は、サーバーが管理する認可レコードを参照する必要があります。リソースサーバーが信頼できない表示文字列から金額、通貨、受取人を再構築するような設計にしてはいけません。

2. トークンをクライアント鍵にバインドする

クライアントは保護されたストレージに秘密鍵を作成し、トークンエンドポイントにDPoPプルーフJWTを送信します。プルーフには、HTTPメソッド、ターゲットURI、発行時刻、一意のプルーフ識別子、および公開鍵が含まれます。認可サーバーはプルーフを検証し、確認クレーム(confirmation claim)がその鍵を参照するトークンを発行します。したがって、秘密鍵なしでコピーされたベアラートークンは、保護されたリクエストには不十分となります。

DPoPはアプリケーションレベルの送信者制約(sender constraint)であり、TLS、セキュアな鍵ストレージ、認可ポリシー、またはデバイス失効を置き換えるものではありません。鍵はクライアントインスタンスの認証手段であり、人間が特定の支払いを承認したことの証明ではありません。

3. すべてのリソースリクエストの検証

クライアントはアクセストークンと最新のDPoPプルーフの両方を送信します。リソースサーバーは、署名、トークンの有効期限、発行者、対象者、確認サムプリント、HTTPメソッド、URI、クロックウィンドウ、必要に応じたnonce、およびプルーフ識別子のリプレイキャッシュをチェックします。別のURI用に再利用されたプルーフや、リプレイウィンドウを過ぎたプルーフは拒否されます。アクセストークンのバインディングと認可詳細の検索は一緒にチェックする必要があります。別のトークンに対する有効なプルーフでは不十分です。

4. 台帳を最終的な決定権者にする

リソースサーバーは、認可レコードID、正規化された金額と受取人のダイジェスト、クライアントインスタンスID、およびべき等キーを含む決済コマンドを作成します。台帳は1つのトランザクション内でコマンドと承認済みレコードを比較します。未使用の認可のみを受け入れ、PENDINGまたはCOMMITTEDを書き込み、変更された金額、送金先、期限切れの承認、または2回目の終端状態への遷移を拒否します。外部プロバイダーがタイムアウトした場合は、UNKNOWNを記録し、再試行する前にステータスを照会または照合します。タイムアウトは請求が発生しなかったことを証明するものではありません。

5. リカバリと失効の処理

制限されたリプレイウィンドウの間プルーフ識別子を保存し、キャッシュをクライアントと発行者ごとに制限します。デバイスを紛失した場合は、ブラウザセッションだけでなく、鍵またはその認可レコードを失効させます。同意ペイロードのハッシュ、表示された値、トークン鍵のサムプリント、リソースの決定、台帳の遷移、オペレーターのアクションについて監査エントリを保持します。秘密鍵と未加工のトークンはマスキング(redact)します。ロールアウトに失敗した場合は、個別に管理されている古いフローを維持しながら、新しい認可詳細タイプを無効化できます。

6. 代替案の比較

payments.writeのような広範なスコープはシンプルですが、1つの金額と1人の受取人を表現できません。いずれにせよ台帳側で別の信頼できるトランザクション認可が必要になります。Mutual TLSはトークンを証明書にバインドでき、管理されたサーバークライアントには魅力的ですが、モバイルデバイスの証明書ライフサイクルとネットワーク終端の難易度が高くなります。DPoPはアプリケーション管理の鍵とHTTPクライアントに適していますが、nonce、クロック、リプレイキャッシュ、および鍵紛失の処理を明示的に実装する必要があります。

質の高い模範解答

「私は4つの決定事項を分離します。第1に、RARは正確な金額、通貨、受取人、およびサーバーリクエストIDを含む型定義された決済認可を伝送します。同意画面は正規の値をレンダリングし、認可サーバーは制限を検証します。第2に、モバイルクライアントは保護された鍵とDPoPプルーフを使用し、発行されたトークンはその鍵にバインドされます。第3に、リソースサーバーはサーバー管理の認可レコードを読み込む前に、トークン、プルーフ、URI、時間、nonce、およびプルーフのリプレイを検証します。最後に、決済台帳はそのレコードとコマンドをアトミックに比較し、1回限りのべき等な終端遷移を許可します。鍵のないコピーされたトークン、変更された受取人、重複したプルーフ、または2回目の送信は拒否されます。プロバイダーのタイムアウトはUNKNOWNとなり、ブラインドに再試行されることはなく、必ず照合されます。拒否されたリプレイ、nonceエラー、認可の不一致、重複コマンド、不明な結果、失効の伝播を計測し、ロールバックスイッチを備えたカナリアリリースで新しいRARタイプを導入します。」

よくある間違い

  • 金額と受取人をスコープに入れる → スコープが非構造化な文字列になり、型チェックされた検証が失われる → 登録されたauthorization-detailsスキーマとサーバー管理のレコードを使用する。
  • DPoPを人間の同意として扱う → 鍵の所持はユーザーが承認した内容を証明しない → 同意、トークンバインディング、および台帳の認可を分離しておく。
  • ゲートウェイでのみDPoPを検証する → 内部の呼び出し元や代替ルートがチェックをバイパスする可能性がある → すべてのリソース境界でバインディングを強制し、署名された決定IDを渡す。
  • プロバイダーのタイムアウトを新しい支払いとして再試行する → 最初のリクエストがコミットされている可能性がある → ステータスを照会し、べき等キーを使用し、明示的なUNKNOWN状態を保持する。
  • プルーフIDを永続的にキャッシュする → メモリが増大し、ポリシー変更の推論が困難になる → プルーフの有効期間とクロックポリシーに紐づく制限されたリプレイウィンドウを使用する。

フォローアップ質問と回答

攻撃者がアクセストークンとDPoPプルーフの両方を盗んだ場合はどうなりますか?

プルーフ識別子の再利用を拒否し、メソッド、URI、時間、およびnonceの制約を強制します。プルーフの有効期間を短く保ち、リクエストごとに新しいプルーフを要求します。秘密鍵も侵害されている場合は、鍵のバインディングと認可レコードを失効させます。DPoP単体では侵害された鍵を復旧できません。

なぜ支払いをJWTスコープでエンコードしないのですか?

スコープはおおまかな権限には有用ですが、送金には型付けされたフィールド、検証、表示、および安定したサーバーレコードが必要です。スコープ文字列を広範な機能権限として残しつつ、RARと台帳で正確なトランザクションを強制することができます。

リソースサーバーと認可サーバーのデータに不一致がある場合はどうなりますか?

サーバー管理の認可IDとバージョン管理されたレコードを使用します。リソースサーバーは、高リスクのアクションに対してレコードが利用できない場合、またはそのバージョンが古い場合に、フェイルクローズ(fail closed)しなければなりません。台帳はトランザクション内でバージョンを再チェックするため、キャッシュされた古い決定によって変更された支払いがコミットされることはありません。

DPoPはすべてのリプレイを防ぎますか?

いいえ。攻撃者が秘密鍵を持っていない場合にコピーされたトークンのリプレイを減らし、サーバーが再利用されたプルーフを検出できるようにします。鍵の正当な所有者(または悪意ある所持者)が許可されたコマンドを繰り返すことを防ぐものではないため、台帳のべき等性、1回限りの認可、レート制限、およびデバイスの失効が依然として必要です。

公開情報ソース

関連する質問