Skip to content

送信者制約 — DPoP と mTLS の選び方

保護のない bearer トークンは bearer-authoritative です — バイト列を持つ者が API を呼べてしまいます。トークンが漏れると(ログ、中継 proxy、ブラウザ拡張、サードパーティ SDK)、漏らした側は有効期限まで全アクセス権を握ります。

送信者制約付き アクセストークンは、正規クライアントが保有する鍵にバインドされます。バイト列が漏れても、攻撃者は鍵を一緒に盗まないと使えません。本ページは選定ガイドです — それぞれの仕組みは DPoPmTLS の専用ページに分かれています。

トークン 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(htmhtuiatjti、任意の athnonce)で、HTTP ヘッダ DPoP: に乗せます。プレーン HTTPS で動作し、TLS クライアント証明書を要しません。詳細は DPoP を参照してください。

mTLS(RFC 8705)は、TLS ハンドシェイクで提示した X.509 証明書にトークンをバインドする方式です。OP は証明書の SHA-256 thumbprint を cnf.x5t#S256 として発行 token に書き込み、リソースサーバは観測した証明書の thumbprint を照合します。詳細は mTLS を参照してください。

比較

観点DPoPmTLS
仕様RFC 9449RFC 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 窓、任意のサーバ nonceTLS セッション再利用 + 証明書 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_authself_signed_tls_client_auth)、cnf.x5t#S256、リバースプロキシ構成。

次に読む