RFC 9449: OAuth 2.0 DPoPの意識されていない可能性に迫る
ritouです。
今日はRFC9449で定義されているOAuth 2.0 DPoPという仕様を取り上げます。
Sender-constraining OAuth Token
This document describes a mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level.
とある通り、DPoPの目的は、アプリケーションレイヤだけで実現できるSender-Constrained Tokenの実現です。
アクセストークン、リフレッシュトークン発行時にDPoP Proof JWTを指定することで、トークンをPoP Key(秘密鍵)に紐づけることができ、リソースアクセスやトークンリフレッシュの際にリクエストの送信元がPoP Keyを管理している存在であることを検証できます。
実際にOktaなどでは動作を確認できる状況にあるようです。
今回は、これらのドキュメントでは触れられていない部分に注目します。
- 認可コードとPoP Keyの紐付け
- PKCEとの関係
それでは始めましょう。
認可コードと鍵(ペア)の紐付け
仕様の "Section 10. Authorization Code Binding to a DPoP Key" をみてみましょう。
Binding the authorization code issued to the client's proof-of-possession key can enable end-to-end binding of the entire authorization flow.
PoP Keyは認可コードとの紐づけも可能です。
This specification defines the dpop_jkt authorization request parameter for this purpose.
dpop_jkt
というパラメータを認可リクエストに指定します。
The value of the dpop_jkt authorization request parameter is the JWK Thumbprint [RFC7638] of the proof-of-possession public key using the SHA-256 hash function, which is the same value as used for the jkt confirmation method defined in Section 6.1.
で、この dpop_jkt
というのがPoP KeyのThumbprint、つまり公開鍵情報をごにょごにょしてSHA-256ハッシュ関数にかけてアレしてあれしたやつです。
この Thumbprint はどちらかというと認可サーバーやリソースサーバーの方がハンドリングする必要がある値なので、クライアント側の観点ではここが初見となるかもしれません。
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz\
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb\
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM\
&code_challenge_method=S256\
&dpop_jkt=NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs HTTP/1.1
Host: server.example.com
認可フロー全体で見ると、
- クライアントは認可リクエストに
dpop_jkt
を指定 - 認可サーバーは認可コードに
dpop_jkt
を紐付け - クライアントはアクセストークンリクエストに
dpop_jkt
の対象であるPoP Keyを用いて DPoP Proof JWT を指定 - 認可サーバーは DPoP Proof JWT 自体の検証、認可コードに紐づけられた Thumbprint との一致を検証し、今度はアクセストークン、リフレッシュトークンをPoP Keyと紐付けた上で発行
- クライアントはリソースアクセス、トークンリフレッシュ時に対象であるPoP Keyを用いて DPoP Proof JWT を指定
という流れになるわけです。
イメージできない人のために、(結局イメージできてる人しか理解できないであろう)シーケンス図を用意しました。
おわかりいただけましたでしょうか。
DPoPの仕様だけで、認可コードからアクセストークン、リフレッシュトークンまで全てSender-constrained Tokenにできるのです。
PKCEとの関係
もう少し仕様を見てみましょう。
Note that the dpop_jkt authorization request parameter MAY also be used in combination with Proof Key for Code Exchange (PKCE) [RFC7636], which is recommended by [SECURITY-TOPICS] as a countermeasure to authorization code injection.
紹介した認可リクエストのサンプルの通り、PKCEと組み合わせることもできます。
The dpop_jkt authorization request parameter only provides similar protections when a unique DPoP key is used for each authorization request.
認可リクエスト毎にユニークなPoP Keyが使われるのであれば、PKCEと同等の保護ができるとあります。
ここから、両者の違いを整理してみましょう。
PKCE | DPoP | |
---|---|---|
鍵 | code_verifier |
PoP Key(Key Pair) |
認可リクエストに含まれるもの |
code_challenge (code_verifier のハッシュ値), code_challenge_method
|
dpop_jkt |
認可コードと紐づけられるもの | code_challenge |
dpop_jkt |
トークンリクエストに含まれるもの | code_verifier |
DPoP Proof JWT |
鍵の生成タイミング | 認可リクエスト毎 | 任意 |
どちらも鍵情報に対して一意に紐づけられつつ鍵情報そのものを入手できない値を認可リクエストに指定、認可コードに紐づけ、トークンエンドポイントで検証した上でトークンを発行できます。
RFC7636には
A unique code verifier is created for every authorization request...
とあるように、認可リクエスト毎にユニークな値を利用することが明記されています。
DPoP の PoP Key 管理は任意であり、どちらかといえば端末/アプリ毎に生成して管理しておくイメージかもしれませんが、ここを認可リクエスト毎にすることで、PKCEと同等、いや公開鍵暗号の鍵ペアを利用している分だけPKCEよりも高度に認可コードを保護できると言えるかもしれません。
実際にはPKCEもDPoPもちゃんと毎回パラメータを生成しているかはClientに委ねられる仕組みではあるのでちゃんとやろうと思うと重複管理などが必要になるのかもしれませんが、DPoPだけでPKCE相当の認可コードの保護ができるんだよというのを覚えておくと、いつか私と会話する機会があった時に話が弾むかもしれません。
まとめ
- DPoPはSender-constrainedなトークンを実現する仕組みとして有名
- 認可コードとの紐付けも可能
- PoP Key生成のタイミング次第ではPKCE相当の効果が得られる
ではまた。
Discussion