RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)
RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)
概要
OAuth 2.0[1] のアクセストークンのタイプとしては Bearer トークン[2]が最も一般的と思われる。 これはシンプルな仕組みである一方で、本来のアクセストークンの発行を受けた者以外であっても取得さえしてしまえばリソースへのアクセス権限を得られてしまうという問題がある。
そこで本仕様では使用者を制限する仕組みを備えたアクセストークンのタイプとして DPoP (Demonstrating Proof of Possession) を定義する。主な処理の流れは以下の通りである。
基本的なコンセプトはアクセストークンの利用者を制限するために、アクセストークンをクライアント側で発行したキーペアの公開鍵と紐づけておくことである。
クライアントはアクセストークン取得時にはアクセストークンに紐づける公開鍵を認可サーバー側に登録し、以降アクセストークン利用時には対応する秘密鍵の所有を認可サーバーに証明するが、どちらの場合も DPoP Proof JWT と呼ばれる JWT が用いられる。
DPoP Proof JWT
DPoP Proof JWT は上記のトークンエンドポイントやリソースへのリクエストとともに用いられる JWT (JWS) である。クライアントは DPoP Proof JWT を必要に応じて生成し認可サーバーやリソースサーバーに送信する。
DPoP Proof JWT のヘッダは以下の値を持つ必要がある。
value | description |
---|---|
typ | 固定値 dpop+jwt
|
alg | 署名アルゴリズムの識別子を JSON Web Signature and Encryption Algorithms に従って指定する。ただし非対称鍵が用いられるものでなければならない |
jwk | 公開鍵を JWK[1] で示す |
また、DPoP Proof JWT のペイロードは以下のクレームを持つ必要がある
claim | description |
---|---|
jti | DPoP Proof JWT の識別子 |
htm | この JWT が用いられる HTTP リクエストのメソッド (GET, POST, ...) |
htu | この JWT が用いられる HTTP リクエスト先の URL のうち、クエリとフラグメント部を除いた部分 |
iat | JWT の生成時刻[2] |
ath | アクセストークンの SHA-256 ハッシュ値。トークンタイプが DPoP のアクセストークンを用いて保護されたリソースにアクセスする場合に必須。 |
nonce | 認可サーバーまたはリソースサーバーがレスポンスで DPoP-Nonce ヘッダを返却する場合には必須。直近のリクエストで得た DPoP-Nonce の値を指定する |
以下は Section 4.2 に記載のヘッダとペイロードの例である
{
"typ":"dpop+jwt",
"alg":"ES256",
"jwk": {
"kty":"EC",
"x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
"y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
"crv":"P-256"
}
}
.
{
"jti":"-BwC3ESc6acc2lTc",
"htm":"POST",
"htu":"https://server.example.com/token",
"iat":1562262616
}
-
RFC 7519 Section 4.1.6 ↩︎
トークンエンドポイント
リクエスト
クライアントがトークンエンドポイントで公開鍵に紐づいたアクセストークンを要求するとき、各 grant_type 毎のリクエストに加え、DPoP
ヘッダ[1]に (有効な) DPoP Proof JWT を含める。
以下は Section 5 に記載のリクエスト例である。
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj\
oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia\
WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg\
4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg
grant_type=authorization_code\
&client_id=s6BhdRkqt\
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb\
&code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-
レスポンス
認可サーバーは必要な検証 (DPoP Proof JWT の検証やその他通常のトークンエンドポイントでのリクエストの検証)に成功した場合、認可サーバーはアクセストークンを公開鍵に紐づけて発行し、token_type
を DPoP
として返却する。以下は Section 5 に記載のレスポンス例である。
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU",
"token_type": "DPoP",
"expires_in": 2677,
"refresh_token": "Q..Zkm29lexi8VnWg2zPW1x-tgGad0Ibc3s3EwM_Ni4-g"
}
DPoP Proof JWT の検証に失敗した場合、認可サーバーは RFC 6749 Section 5.2 にしたがってエラーを返却する。ここでレスポンスの error
パラメータの値は invalid_dpop_proof
とする。
リフレッシュトークンの場合の注意点
コンフィデンシャルクライアントに対してリフレッシュトークンが発行されるとき、リフレッシュトークンは公開鍵に紐づけない。これはリフレッシュトークンの利用時にはクライアント認証が求められており[2]、Bearer タイプのアクセストークンと異なり使用者制限トークンとなっているからである。
パブリッククライアントに対してリフレッシュトークンが発行されるときはトークンリクエストに含まれる DPoP Proof JWT の公開鍵に紐づける。クライアントはリフレッシュトークンを用いるときは発行を受けたときと同じ秘密鍵で署名した DPoP Proof JWT を送信する必要がある。
また grant_type
が refresh_token
の場合[3]、認可サーバーはトークンエンドポイントへのリクエストヘッダに有効な DPoP
が含まれている場合でも、DPoP
以外のトークンタイプのアクセストークン (Bearer トークンなど) を発行してもよい。このときレスポンスボディの token_type
は実際に発行されるアクセストークンのタイプとなる。ただしクライアントがパブリッククライアントの場合は、リフレッシュトークンはやはり公開鍵に紐づけられたものになる。
保護されたリソースへのアクセス
クライアントが保護されたリソースへのアクセスを行うのに DPoP アクセストークンを用いる場合には Authorization ヘッダにアクセストークンを含めるが、このとき認証スキームは DPoP
とする。すなわち、Authorization ヘッダの形式は DPoP <アクセストークン>
となる。
加えて、トークンエンドポイントの場合と同様に DPoP Proof JWT を DPoP
リクエストヘッダに含める。このとき JWT のペイロードは ath
クレームとしてアクセストークンの SHA-256 ハッシュ値を持たなければならない。
以下は Section 7.1 に記載のリクエストの例である。
GET /protectedresource HTTP/1.1
Host: resource.example.org
Authorization: DPoP Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIj\
oiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0Z\
WRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOCwiYXRoIjoiZlVIeU8ycjJaM0RaNTNF\
c05yV0JiMHhXWG9hTnk1OUlpS0NBcWtzbVFFbyJ9.2oW9RP35yRqzhrtNP86L-Ey71E\
OptxRimPPToA1plemAgR6pxHF8y6-yqyVnmcw6Fy1dqd-jfxSYoMxhAJpLjA
DPoP-Nonce
認可サーバーやリソースサーバーの保護されたリソースのエンドポイントにおいて、認可サーバーやリソースサーバーはオペークな DPoP-Nonce
を発行し、クライアントからのリクエストに対してレスポンスヘッダ DPoP-Nonce
を用いて返却してもよい[1]。
DPoP-Nonce
を受け取ったクライアントは、後続の DPoP Proof JWT を必要とするエンドポイントで、当該 JWT の nonce
クレームに受け取った値を含める。認可サーバーやリソースサーバーは有効な nonce
クレームが含まれない場合にはリクエストを拒否しなければならない。
DPoP-Nonce
の仕組みを導入することによって、DPoP Proof JWT の有効期限をサーバー側でコントロールすることが可能になる[2]。
認可サーバーやリソースサーバーでは発行した DPoP-Nonce を例えばその後数分間だけ保持する実装をしておき、クライアントのリクエストの DPoP Proof JWT に含まれる nonce
が、そのいずれかに一致することを確認するような実装になるだろう。クライアント側は Nonce は保持せずに、必要に応じて認可サーバーから取得する実装にするのがシンプルであるように思われる。
認可サーバー
DPoP Proof JWT が有効な nonce を含まない場合に認可サーバーは RFC 6749 Section 5.2 にしたがってエラーを返却する。ここで error
パラメータは use_dpop_nonce
とする。またこれに加えて DPoP-Nonce
レスポンスヘッダで nonce を返却する。
クライアントはこれをうけて nonce
クレームを持つ DPoP Proof JWT を生成し、再度トークンエンドポイントへのリクエストを行えばよい。
以下は Section 8 に記載の、DPoP Proof JWT が有効な nonce を含まない場合のエラーレスポンスである。
HTTP/1.1 400 Bad Request
DPoP-Nonce: eyJ7S_zG.eyJH0-Z.HX4w-7v
{
"error": "use_dpop_nonce",
"error_description": "Authorization server requires nonce in DPoP proof"
}
これを受けてクライアントは、例えば以下のようなペイロードを持つ DPoP Proof JWT を発行すれば良い
{
"jti": "-BwC3ESc6acc2lTc",
"htm": "POST",
"htu": "https://server.example.com/token",
"iat": 1562262616,
"nonce": "eyJ7S_zG.eyJH0-Z.HX4w-7v"
}
認可サーバーは各エンドポイントが成功レスポンスを返却する場合にも DPoP-Nonce を返却してもよいが、ここで返却された DPoP-Nonce がその後クライアントが次に認可サーバーにリクエストする時点まで有効であるとは限らない。無効になっている場合にはエラー use_dpop_nonce
とともに新しい DPoP-Nonce を受け取るので、それを用いればよい。
リソースサーバー
リソースサーバーは保護されたリソースへのリクエストを受け、DPoP Proof JWT の nonce
クレームに有効な値が含まれなかった場合にはステータスコード 401 でエラーを返却するが、そのときに DPoP-Nonce
ヘッダで nonce を含めることができる。以下は Section 9 に記載のレスポンスの例である。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="use_dpop_nonce", error_description="Resource server requires nonce in DPoP proof"
DPoP-Nonce: eyJ7S_zG.eyJH0-Z.HX4w-7v
クライアントは認可サーバーのトークンエンドポイントと同様、ここで受け取った DPoP-Nonce
を用いて DPoP Proof JWT を生成し、再度当該リソースのリクエストを行えばよい。
[1]
認可コードの公開鍵との紐づけ同様の仕組みを用いて認可コードを公開鍵に紐づける事もできる。
クライアントはあらかじめ用いたい公開鍵の JWK Thumbprint[2] を計算しておき、これを認可エンドポイントのリクエストパラメータ dpop_jkt
で認可サーバーに送信する。認可サーバーは発行する認可コードをこの dpop_jkt
に紐づけておく。
トークンエンドポイント (grant_type
が authorization_code
の場合) では認可サーバーは受け取った DPoP Proof JWT のヘッダの jwk
から JWK Thumbprint を計算し、これが認可コードに紐づけられたものと同じであることを確認する。
認可サーバーが Pushed Authorization Request[3] をサポートする場合、Pushed Authorization Request Endpoint では dpop_jkt
を受け付けるのに加え、DPoP
リクエストヘッダでの DPoP Proof JWT の指定もサポートしなければならない。