DPoP(OAuth 2.0 Demonstrating Proof of Possession)
1. 概要
定義: DPoP(Demonstrating Proof of Possession, RFC 9449)とは、OAuth 2.0 クライアントがアプリケーション層で公開鍵・秘密鍵のペアを用い、「自身がトークンに結び付いた鍵の所有者であること」をリクエストごとに証明させることで、盗まれたトークンの再利用を防ぐ送信者制約(sender-constrained)トークンの仕組みである。
OAuth 2.0 が広く使われる中で最も根強い脅威として残ったのが、Bearer(所持者)トークンの構造的な弱点である。Bearer トークンはその名のとおり「持つ者がすなわち権限者」である。つまりトークンを手に入れさえすれば、誰でも正当なクライアントであるかのように資源サーバ(Resource Server)へアクセスできる。HTTPS で伝送区間を保護しても、トークンがログ・ブラウザストレージ・プロキシキャッシュ・悪性拡張機能・中間者などを通じて漏えいすれば、攻撃者は有効期間の間ずっとそのトークンをそのまま再生(replay)できる。アクセストークンの寿命を短くする緩和策はあるが、これは根本的な解決ではなく、露出窓(window)を縮める対症療法に近い。
この問題に正面から取り組む方向が、トークンを特定の鍵に結び付けることである。トークンを発行する際にクライアントの公開鍵指紋をトークン内に刻み込んでおき、以後の資源アクセスのたびにその公開鍵に対応する秘密鍵で署名した証拠(proof)を併せて提示させれば、トークンだけを盗んだ攻撃者は秘密鍵がなく証拠を作れないため、トークンは無力化される。このような手法を PoP(Proof of Possession)または送信者制約トークンと呼び、従来はmTLS ベース(RFC 8705)の方式が標準として存在していた。しかし mTLS は伝送層でクライアント証明書を要求するため、TLS 終端点(ロードバランサ・CDN・リバースプロキシ)に証明書情報が従属し、ブラウザベースの SPA(Single Page Application)やモバイルの公開クライアントでは適用が難しいという現実的な制約が大きかった。
DPoP はまさにこの空白を埋めるために、2023年9月に IETF がRFC 9449として標準化したアプリケーション層の PoP 手法である。伝送層ではなく HTTP ヘッダに JWT 形式の証拠を載せて送るため TLS 終端構造に影響されず、ブラウザでも Web Crypto API で鍵を非抽出(non-extractable)形式で生成・保管できるため公開クライアントに特に適する。技術士の観点から、DPoP は「トークン盗難を前提とする(assume breach)ゼロトラスト時代に、公開クライアントまで含めてトークン再利用を遮断する軽量な標準」として位置づけられる。
2. 動作原理と構成要素
2.1 全体構造
DPoP は、認可サーバ(Authorization Server, AS)・クライアント・資源サーバ(Resource Server, RS)の三者の間で、公開鍵指紋を媒介にトークンと鍵を結束する。中核となる考えは二つである。第一に、クライアントが自分だけが知る秘密鍵で各リクエストに署名したDPoP Proof JWTを作る。第二に、発行されたアクセストークンの中にその秘密鍵に対応する公開鍵の指紋(cnf.jkt)を入れてトークンと鍵を結び付ける。資源サーバはリクエストごとに「トークンに刻まれた指紋」と「証拠に含まれる公開鍵の指紋」が一致するかを照合する。
flowchart LR
C["クライアント(秘密鍵を保有)"]
AS["認可サーバ(AS)"]
RS["資源サーバ(RS)"]
C -->|"① トークン要求 + DPoP Proof(公開鍵を含む)"| AS
AS -->|"② DPoP バインディングトークン(cnf.jkt=公開鍵指紋)"| C
C -->|"③ 資源要求 + アクセストークン + DPoP Proof(ath を含む)"| RS
RS -->|"④ 指紋照合成功時に資源応答"| C
AS -.->|"鍵指紋の刻印"| RS
この構造で信頼の根は、「秘密鍵はクライアントだけが持つ」というただ一つの仮定である。公開鍵・トークン・証拠はすべてネットワークを漂いうるが、秘密鍵で署名を作れる主体は正当なクライアントだけであるため、サーバは署名検証だけで「このリクエストを送った者がトークンに結び付いた鍵の所有者である」と確信できる。したがって秘密鍵の安全な保管(非抽出鍵・セキュリティハードウェア)が、DPoP セキュリティの事実上すべてを支える。
クライアントはまず鍵ペアを作り(①)、トークン要求に公開鍵(JWK)を載せた DPoP Proof を付けて送る。AS は公開鍵のRFC 7638 SHA-256 サムプリント(thumbprint)を計算し、アクセストークンの cnf(confirmation)クレームに jkt として刻み込んだうえでトークンを返す(②)。以後クライアントは資源アクセスのたびに同じ秘密鍵で新しい Proof に署名して併せて送り(③)、RS は Proof の公開鍵サムプリントがトークンの cnf.jkt と同じかを検証したうえでのみ資源を渡す(④)。トークンタイプも Bearer ではなく DPoP に変わり、リクエストヘッダは Authorization: DPoP <token> と DPoP: <proof-jwt> の二つを併せて載せる。
2.2 DPoP Proof JWT の構造
DPoP Proof はリクエストごとに新しく生成される短寿命の JWT である。JWT の保護ヘッダ(protected header)には typ: "dpop+jwt" と署名アルゴリズム(alg)、そして公開鍵全体(jwk)が入る。重要なセキュリティ規則として、このヘッダには公開鍵の公開成分のみを含めなければならず、秘密鍵成分が混ざっていれば検証者は直ちに拒否しなければならない(RFC 9449 §4.2)。公開鍵をリクエストごとに含める設計のおかげで、サーバは別途の鍵登録・照会なしに証拠自体から鍵を取り出して検証できる。
ペイロード(クレーム)には、リクエストを特定の文脈に結束する値が入る。各要素を原理とともに見ると次のとおりである。htm(HTTP method)と htu(HTTP URI)は、この証拠がどのメソッドでどのエンドポイントに向けたものかを釘付けにし、一リクエスト用の証拠を別のエンドポイントへ使い回せないようにする。iat(issued at)は発行時刻で、サーバが許容時間窓を外れた古い証拠を濾し取る根拠となる。jti(JWT ID)は証拠ごとに一意な識別子で、サーバが最近見た jti を記憶して同一証拠の再生(replay)を検知できるようにする。資源要求の段階では ath(access token hash)クレームが追加されるが、これは併せて提示したアクセストークンの base64url(SHA-256(access_token)) 値であり、証拠と特定のトークンを追加で結束する。サーバが nonce を要求すれば nonce クレームも含まれる。
| 位置 | フィールド | 意味 | セキュリティ上の役割 |
|---|---|---|---|
| header | typ |
dpop+jwt 固定 |
タイプ混同(JWT 混用)の防止 |
| header | jwk |
公開鍵(公開成分のみ) | サーバが即座に署名検証 |
| payload | htm/htu |
HTTP メソッド/URI | エンドポイント間で証拠を専用化 |
| payload | iat |
発行時刻 | 許容時間窓を超えた証拠の遮断 |
| payload | jti |
証拠固有 ID | 再生検知(サーバキャッシュ照合) |
| payload | ath |
アクセストークンハッシュ | 証拠↔トークン結束(資源要求時) |
| payload | nonce |
サーバ提供ナンス | サーバ主導の再生防止・時計同期不要 |
2.3 鍵バインディングと nonce の仕組み
トークンと鍵の結束は cnf.jkt という一組の値で行われる。AS は Proof の jwk に対してRFC 7638 SHA-256 サムプリントを算出し、これを RFC 7800 が定義する cnf(confirmation method)クレーム内の jkt(JWK SHA-256 Thumbprint)フィールドとしてアクセストークンに植える。資源サーバは入ってきた Proof の公開鍵で同じサムプリントを再計算し、トークンの cnf.jkt とバイト単位で比較する(RFC 9449 §7)。二つの値が異なれば、そのトークンはこの鍵の所有者が提示したものではないため拒否される。アクセストークンが JWT ではなく不透明(opaque)トークンであれば、RS はトークンイントロスペクション(RFC 7662)応答から同じ cnf.jkt を受け取って照合する。
nonce は再生防止をサーバが主導するようにする仕掛けである。クライアントが iat だけで新鮮さを証明すれば、サーバはクライアントの時計を信頼せねばならず、許容時間窓の分だけ再生の余地が残る。これを無くすため、サーバは DPoP-Nonce 応答ヘッダでサーバが生成したナンスを下ろし、クライアントは次の Proof の nonce クレームにその値をそのまま入れねばならない。サーバがナンスを要求したのに無いか古いナンスであれば、HTTP 401 とともに use_dpop_nonce エラーを返し(RFC 9449 §8)、クライアントは新しいナンスで直ちに再試行する。実務の実装は通常、32バイトの base64url ナンスを5分前後の短い TTLで発行・回転させ、サーバが発行したことのないナンスを含む証拠をすべて濾し取る。
2.4 具体例: 実際のリクエストが載ったヘッダ
概念を数値とともに見ると構造が明らかになる。クライアントがアクセストークン eyJ... で GET /userinfo を呼ぶ実際の HTTP リクエストは、おおよそ次のような姿である。Authorization ヘッダのスキームが Bearer ではなく DPoP であり、その下に別途の DPoP ヘッダで証拠 JWT が併せて載るという点が核心である。
GET /userinfo HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJhbGciOiJSUzI1NiIsImtpZCI6...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7...
ここで DPoP ヘッダをデコードすると、保護ヘッダは {"typ":"dpop+jwt","alg":"ES256","jwk":{...公開鍵...}} であり、ペイロードは {"jti":"-BwC3ESc6acc2lTc","htm":"GET","htu":"https://api.example.com/userinfo","iat":1762041600,"ath":"fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo","nonce":"eyJ7S_..."} のようになる。資源サーバは jwk で署名を検証したのち、htm・htu が実際のリクエストと同じか、ath が提示されたトークンの SHA-256 ハッシュと同じか、そして公開鍵サムプリントがトークンの cnf.jkt と同じかを順に照合する。jti は例えば120秒の許容窓の間キャッシュに保管し、同じ値が再び来れば再生と判断する。公開鍵をリクエストごとに載せる費用(ES256 基準で数百バイト)は訝しく思えるかもしれないが、これはサーバが事前の鍵登録・照会なしに無状態(stateless)で検証できるようにする設計上の交換である。
3. 処理手順(リクエストのライフサイクル)
DPoP のセキュリティは、各段階で何を検証するかにかかっている。下のフローは、資源要求一件が通過せねばならない検証関門を示す。
sequenceDiagram
participant C as クライアント
participant RS as 資源サーバ
C->>C: "秘密鍵で Proof JWT に署名(htm,htu,iat,jti,ath,nonce)"
C->>RS: "Authorization: DPoP token + DPoP: proof"
RS->>RS: "① 署名検証(jwk で Proof 署名を確認)"
RS->>RS: "② htm/htu が実際のリクエストと一致するか"
RS->>RS: "③ iat 時間窓・jti 重複(再生)検査"
RS->>RS: "④ ath=SHA-256(token) 一致確認"
RS->>RS: "⑤ jwk サムプリント == トークン cnf.jkt"
alt すべての検証を通過
RS-->>C: "200 資源返却"
else ナンス必要/不一致
RS-->>C: "401 use_dpop_nonce + DPoP-Nonce"
C->>RS: "新しい nonce で Proof を再署名して再試行"
end
署名検証(①)は、証拠自体の完全性と鍵所有証明の根である。htm/htu 照合(②)は、証拠が今このエンドポイントに向けたものであることを保証し、エンドポイント間の流用(cross-endpoint reuse)を防ぐ。時間窓・jti 検査(③)は、同じ証拠を二度使う再生を遮断するが、サーバは通常、数十秒〜数分の許容窓の間に見た jti をメモリストア(例: Redis)に保管する。ath 確認(④)は、証拠がまさにこのアクセストークンと共に使われるように結び付け、最終的にサムプリント照合(⑤)がトークンと鍵の所有一致を確定する。五つの関門のうち一つでも失敗すれば資源は渡されない。
一つ留意すべき運用ポイントは、リフレッシュトークンの結束である。公開クライアントでは、アクセストークンだけでなくリフレッシュトークンも DPoP 鍵に結び付き、リフレッシュトークンが漏えいしても鍵なしでは新しいアクセストークンを得られない。一方、機密クライアント(バックエンド)はすでにクライアント認証手段を持つため、リフレッシュトークンはクライアント資格で、アクセストークンは DPoP 鍵で結束されるように役割が分かれる。
4. 比較: Bearer · mTLS 結束 · DPoP
三つの方式の違いは、「何でトークンを結び付けるか」と「どの層で証明するか」で分かれる。Bearer は何も結び付けず盗難に無防備であり、mTLS 結束は伝送層証明書で強く結び付けるが公開クライアント・TLS 終端構造では扱いにくい。DPoP はアプリケーション層の鍵で結び付けて公開クライアントまで包摂するが、mTLS ほど伝送区間自体を保護しないという点が本質的なトレードオフである。すなわち DPoP は「盗難後の再利用」を防ぐが、活性セッションをリアルタイムで中継する精巧な MITM まですべて防ぐわけではない(そのため HTTPS・nonce が前提となる)。
| 区分 | Bearer | mTLS 結束(RFC 8705) | DPoP(RFC 9449) |
|---|---|---|---|
| 結束媒介 | なし | クライアント TLS 証明書 | アプリケーション層の鍵ペア |
| 証明層 | - | 伝送(TLS) | アプリケーション(HTTP ヘッダ) |
| 公開クライアント適合性 | 高い | 低い | 高い(ブラウザ・モバイル) |
| TLS 終端構造の影響 | なし | 大(終端点に証明書従属) | なし |
| 盗難トークンの再利用 | 脆弱 | 遮断 | 遮断 |
| 主な費用 | - | PKI・インフラ | リクエストごとの署名・nonce 往復 |
実務的な含意は明確である。サーバ間(B2B)通信のように PKI がすでに整い TLS 終端を直接統制する環境では、mTLS 結束が堅牢である。逆に多数のブラウザ・モバイルアプリが接続する公開 API、特に金融分野のオープンバンキング・マイデータのように高価値トークンを公開クライアントが扱う領域では、DPoP が現実的な選択である。実際に英国オープンバンキングおよび FAPI 2.0(Financial-grade API)セキュリティプロファイルは、送信者制約の手段として mTLS と DPoP の双方を許容し、環境に合わせて選べるようにしている。
4.1 具体的な攻撃シナリオで見る効果
違いを数値のあるシナリオで見ると説得力が増す。アクセストークン寿命を15分に設定したサービスを仮定しよう。Bearer 方式では、攻撃者がログや XSS でトークンを持ち出して自分のサーバへ送れば、残りの有効期間(最大15分)の間どの端末からでもそのトークンをそのまま再生して資源にアクセスできる。トークン寿命を1分に縮めれば窓は狭まるが、その分トークン再発行が頻繁になり AS 負荷とユーザ体験が悪化する。すなわち Bearer では寿命短縮はセキュリティと費用のゼロサム交換である。
同じ攻撃を DPoP がどう無力化するかを見よう。攻撃者がトークンを持ち出しても、資源サーバはリクエストに付いた Proof JWT の署名を、攻撃者が持たない秘密鍵なしには作れないことを確認する。攻撃者が被害者の過去の Proof まで併せて盗んで再利用しようとしても、htm/htu が異なるエンドポイントなら拒否され、同じエンドポイントでも jti 重複・iat 時間窓・nonce 不一致で再生が阻まれる。結果として DPoP は、トークン寿命を無理に縮めなくても「持ち出し後の外部再利用」という最もありふれた盗難経路を構造的に閉じ、セキュリティと使い勝手のゼロサムを緩和する。
5. 深化: 標準化動向と適用事例
DPoP は 2023年9月に RFC 9449 として確定したことで「提案」ではなく引用可能な標準となり、以後セキュリティプロファイルが急速にこれを取り込んだ。代表的にFAPI 2.0 Security Profileは、アクセストークンを必ず送信者制約で発行するよう要求し、その手段として mTLS または DPoP を指定する。これはオープンバンキング・マイデータのように、トークン一つがすなわち口座・個人情報アクセス権となる高リスクドメインで Bearer を事実上禁ずる流れであり、韓国国内の金融セキュリティガイドも同じ方向をたどっている。
実装エコシステムも成熟した。サーバ側では Spring Security(6.x 以降の DPoP-bound Access Token 対応)、Keycloak、Curity、Authlete などが発行・検証を内蔵し、クライアント側ではブラウザのWeb Crypto API で非抽出鍵を作って IndexedDB に保管するパターンが定着した。非抽出鍵は JavaScript が秘密鍵の原文を読み取れないようにするため、XSS でトークンが漏えいしても攻撃者が鍵を持ち去って別の端末で証拠を偽造するのは難しい。ただし XSS が同じブラウザの中で証拠生成関数を呼んでリクエストを代理実行する攻撃までは防げないため、DPoP は XSS 対策ではなく、トークン持ち出し(exfiltration)後の外部再利用を防ぐ手段として理解せねばならない。
韓国国内の文脈でも DPoP の適切性は高い。マイデータ(本人信用情報管理業)は情報主体の委任を受けて金融機関の個人信用情報を API で伝送される構造であり、伝送トークン一つがすなわち広範な個人情報アクセス権となる。このようなトークンが公開クライアント(モバイルアプリ)で扱われるとき、Bearer をそのまま使えば端末盗難・アプリ複製の際のトークン再利用リスクが大きい。送信者制約を適用すれば、トークンが漏えいしても鍵のない第三者は伝送要求を成立させられないため、韓国国内の金融分野が高価値 API に PoP 系統を要求する流れは、DPoP の導入根拠を強める。
予想される出題方向としては、「Bearer トークンの限界と送信者制約トークンの比較」「DPoP と mTLS 結束の長短所および選択基準」「オープンバンキング/マイデータなど高リスク API におけるトークン保護設計」「ゼロトラスト観点のトークン再利用遮断」などが有力である。答案構成戦略としては、① Bearer の構造的弱点を先に提示し、② DPoP の鍵バインディング・証拠・nonce の仕組みを図とともに展開したのち、③ mTLS 結束との比較表で選択基準を整理し、④ FAPI 2.0・オープンバンキング適用と公開クライアント適合性で締めくくる構成が説得力を持つ。
6. 考慮事項および示唆点
適用戦略(漸進的導入): 既存の Bearer ベースのシステムを一度に変えるのが難しければ、高リスクエンドポイント(送金・個人情報照会)から DPoP を適用し、トークンタイプを
DPoPとして分離して、資源サーバがエンドポイントごとに結束要求水準を変える漸進的な移行が現実的である。AS・RS・クライアント SDK の同時対応が前提であるため、API ゲートウェイで DPoP 検証を共通処理してバックエンドサービスの変更を最小化する設計が有効である。トレードオフ(セキュリティ vs 性能・複雑度): DPoP はリクエストごとに秘密鍵署名とサーバの多段階検証、そして nonce 往復(最初の1回の追加ラウンドトリップ)を要求する。高 TPS 環境では
jti再生キャッシュのストレージ費用と署名検証の CPU 負荷が実質的なボトルネックになりうるため、nonce TTL・許容時間窓・キャッシュ満了ポリシーを遅延とセキュリティの間で調整せねばならない。鍵署名を非抽出鍵・ハードウェア(TPM・Secure Enclave)に委ねれば安全性は上がるが、スループットはさらに制約される。展望(ゼロトラスト・PoP の既定値化): 「ネットワーク境界を信じない」ゼロトラストが広がるにつれ、「トークン所持=権限」という Bearer の仮定は次第に退場し、送信者制約が既定値(secure by default)になる流れである。FAPI 2.0 がこれを義務化したように、今後の高価値 API 標準は PoP を選択ではなく必須として要求する可能性が高い。
連携技術(エコシステム統合): DPoP は単独で完結せず、PKCE(認可コード横取りの防止)、BFF パターン(ブラウザからトークン自体を隠す代替)、トークンイントロスペクション(不透明トークンの
cnf確認)、短いトークン寿命と組み合わせるとき防御が重なる。特に SPA セキュリティでは「トークンをそもそもブラウザに置かない BFF」と「トークンを置くが鍵で結び付ける DPoP」が相互補完的な選択肢であるため、サービスのアーキテクチャ・運用能力に合わせて組み合わせを設計せねばならない。相互運用性と運用成熟度: DPoP は AS・RS・クライアントが同じアルゴリズム(
ES256など)と nonce ポリシーに合意してこそ動作するため、多者間のエコシステムでは対応アルゴリズム・DPoP-Nonce要求の有無・時計誤差許容窓を相互運用プロファイルとして明文化しておくことが現場障害を減らす。また鍵回転(rotation)の際に既存トークンのcnf.jktが無効化される点を考慮し、再認証・再バインディング手順とエラー(use_dpop_nonce,invalid_dpop_proof)処理ロジックをクライアント SDK に標準化しておくことが運用安定性の鍵である。限界の明確な認識: DPoP はトークンの外部再利用を防ぐ道具であり、XSS・悪性端末内部での代理呼び出し、リアルタイム中継型 MITM、ユーザ端末自体の完全掌握まで防ぐ万能手段ではない。したがってコンテンツセキュリティポリシー(CSP)・入力検証・端末完全性点検などと多層防御(defense in depth)で組み合わせてこそ、その価値が全うされる。
参考資料
- RFC 9449, OAuth 2.0 Demonstrating Proof of Possession (DPoP): https://www.rfc-editor.org/rfc/rfc9449
- RFC 7638, JSON Web Key (JWK) Thumbprint: https://www.rfc-editor.org/rfc/rfc7638
- RFC 8705, OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens: https://www.rfc-editor.org/rfc/rfc8705
- Spring Security Reference, OAuth 2.0 DPoP-bound Access Tokens: https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/dpop-tokens.html
- FAPI 2.0 Security Profile (OpenID Foundation): https://openid.net/specs/fapi-2_0-security-profile.html
一言まとめ: DPoP(RFC 9449)とは、クライアントが秘密鍵で署名した Proof JWT とトークンに刻まれた公開鍵指紋(cnf.jkt)をリクエストごとに照合させることで、伝送層に依存せず公開クライアントでも盗まれた OAuth トークンの再利用を遮断する送信者制約トークンの標準である。