送信者制約 — DPoP と mTLS の選び方
保護のない bearer トークンは bearer-authoritative です — バイト列を持つ者が API を呼べてしまいます。トークンが漏れると(ログ、中継 proxy、ブラウザ拡張、サードパーティ SDK)、漏らした側は有効期限まで全アクセス権を握ります。
送信者制約付き アクセストークンは、正規クライアントが保有する鍵にバインドされます。バイト列が漏れても、攻撃者は鍵を一緒に盗まないと使えません。本ページは選定ガイドです — それぞれの仕組みは DPoP と mTLS の専用ページに分かれています。
トークン replay とは何か
攻撃者が漏洩した有効なアクセストークン(ログや侵害された proxy など)を、自分のマシンから再送して API を呼ぶ攻撃です。RS は構文的に有効なトークンを見て応答してしまいます。送信者制約があれば、攻撃者は対応する鍵も提示する必要があり、構造的に replay が成立しません。
proof-of-possession とは
「トークンのバイト列だけでなく、対応する鍵を保有していることを示してください」という考え方の総称です。DPoP / mTLS、それより古い holder-of-key トークンなどはすべて proof-of-possession 方式です。本ライブラリの DPoP / mTLS は、この考え方の現代的な OAuth ネイティブ実装になります。
2026 年に bearer トークンを残すリスク
トークン漏洩は仮想の話ではありません:
- ログ — リバースプロキシのアクセスログ、アプリログ、observability パイプラインは、明示的に剥がさない限り
Authorizationヘッダを残しがちです。ログに残った bearer は TTL いっぱい再利用できてしまいます。 - ブラウザ拡張・SDK — ブラウザ拡張はページと同じプロセス境界の中で動作するため、ページが付ける任意のヘッダを読めます。モバイル SDK もアプリと同じプロセス内に居ます。
- 侵害された中継 — CDN エッジや proxy が 1 つでも侵害されれば、そこを通るすべてのリクエストが攻撃者の手に渡ります。bearer トークンは収穫対象として最も価値があります。
- stage-and-fire — 開発者のマシンに一時的にアクセスできた攻撃者は、トークンをコピーして後日インターネット側から使えます。
構造的な解は、バイト列だけでは不十分にすることです。送信者制約は、すべてのリクエストを正規クライアントが保有する鍵に紐付けることでこれを達成します。漏洩自体は依然として起きますが、漏洩が API 侵害に繋がらなくなります。
送信者制約と TLS の違い
TLS は通信路上のトークンを保護します。一度アプリ層(OP、RS、ロギング middleware、デバッグエンドポイント)に届いた bearer トークンは平文で、それらの場所のいずれかから漏れる可能性が残ります。送信者制約はトークンを end-to-end で守ります。
本ライブラリの 2 つのバインド方式
DPoP(RFC 9449)は、クライアントがリクエスト毎に自分の鍵で署名する方式です。proof は小さな JWT(htm、htu、iat、jti、任意の ath と nonce)で、HTTP ヘッダ DPoP: に乗せます。プレーン HTTPS で動作し、TLS クライアント証明書を要しません。詳細は DPoP を参照してください。
mTLS(RFC 8705)は、TLS ハンドシェイクで提示した X.509 証明書にトークンをバインドする方式です。OP は証明書の SHA-256 thumbprint を cnf.x5t#S256 として発行 token に書き込み、リソースサーバは観測した証明書の thumbprint を照合します。詳細は mTLS を参照してください。
比較
| 観点 | DPoP | mTLS |
|---|---|---|
| 仕様 | RFC 9449 | RFC 8705 |
| 鍵媒体 | クライアントが保持する秘密鍵(署名できる任意の機器) | クライアント TLS 証明書(PKI 発行 / 自己署名) |
| ブラウザ対応 | 可(SPA、モバイル、JWT を署名できるあらゆる主体) | 弱 — ブラウザはクライアント証明書を実用的に提示できない |
| リクエスト毎の追加成果物 | アプリ側で署名する fresh な JWS proof | なし(TLS 層でバインド) |
| proxy / TLS 終端への依存 | なし — プレーン HTTPS で動く | 終端側が証明書をヘッダで前送りする必要あり |
cnf メンバ | cnf.jkt(JWK thumbprint) | cnf.x5t#S256(X.509 thumbprint) |
| リフレッシュトークンバインド既定 | public はバインド、confidential は非バインド(設計判断 #15) | クライアントが token endpoint で mTLS を使ったときにバインド |
| バインドを越えた replay 防御 | jti キャッシュ、iat 窓、任意のサーバ nonce | TLS セッション再利用 + 証明書 thumbprint 照合 |
| FAPI 2.0 Baseline 受理 | 可 | 可 |
| FAPI 2.0 Message Signing | 可(§8 / §9 nonce 併用) | 可 |
FAPI 2.0 Baseline は どちらか一方 での送信者制約付きトークンを要求し、本ライブラリは両方を受理します。op.WithProfile(profile.FAPI2Baseline) は [feature.DPoP, feature.MTLS] に対する RequiredAnyOf を課します。どちらも有効化されていなければ構築時に feature.DPoP を既定メンバーとして選びます。feature.MTLS を明示している場合はそれで制約を満たすため、DPoP は追加されません。
使い分けの指針
選択は、既存インフラから自然に決まることが多いです:
- SPA、モバイル、ブラウザ中心のクライアント → DPoP。ブラウザはクライアント証明書を確実には提示できず、モバイルでの証明書プロビジョニングも UX が悪いためです。DPoP の鍵はメモリかプラットフォームのセキュアストレージに置けます。
- ファーストパーティ API(両端を自分で制御) → DPoP。運用負荷が低く、PKI が不要です。
- 内部 CA を運用済みのバックエンドサービス → mTLS。既存 PKI を再利用でき、新しい鍵管理面を増やしません。
- B2B サービスメッシュ、オープンバンキング、規制環境 → mTLS。多くのケースで規制側がネットワーク層で既に mTLS を要求しており、RFC 8705 はその上に token バインドを重ねるだけで済みます。
- 異種混在環境(SPA + バックエンド) → 両方を有効化。OP が discovery で両方を出し、クライアントごとに使えるほうを選びます。
迷ったら DPoP を既定にしてください。前提となるインフラが少なく、どのクライアント環境でも動きます。
さらに読む
- DPoP (RFC 9449) — proof の構造、replay 防御、
cnf.jkt、サーバ nonce、public / confidential での refresh バインド差。 - mTLS (RFC 8705) — サブモード(
tls_client_authとself_signed_tls_client_auth)、cnf.x5t#S256、リバースプロキシ構成。
次に読む
- ユースケース: FAPI 2.0 Baseline — 送信者制約を有効化した完全な組み込み例。
- DPoP nonce フロー — RFC 9449 §8 / §9 のサーバ供給 nonce パイプライン。
- 設計判断 — public / confidential での refresh バインド差を含む、解決済みの仕様間トレードオフ。