Open6

RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)

yapooyapoo

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 が用いられる。

脚注
  1. RFC 6749 ↩︎

  2. RFC 6750 ↩︎

yapooyapoo

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
}
脚注
  1. RFC 7517: JSON Web Key (JWK) ↩︎

  2. RFC 7519 Section 4.1.6 ↩︎

yapooyapoo

トークンエンドポイント

リクエスト

クライアントがトークンエンドポイントで公開鍵に紐づいたアクセストークンを要求するとき、各 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_typeDPoP として返却する。以下は 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_typerefresh_token の場合[3]、認可サーバーはトークンエンドポイントへのリクエストヘッダに有効な DPoP が含まれている場合でも、DPoP 以外のトークンタイプのアクセストークン (Bearer トークンなど) を発行してもよい。このときレスポンスボディの token_type は実際に発行されるアクセストークンのタイプとなる。ただしクライアントがパブリッククライアントの場合は、リフレッシュトークンはやはり公開鍵に紐づけられたものになる。

脚注
  1. case insensitive なので DPOPdpop などでもよい ↩︎

  2. RFC 6749 Section 6 ↩︎

  3. 原文では明示的にそう書かれているわけではないと思うのだが、文脈的にはリフレッシュトークンの場合に限定されていると思う ↩︎

yapooyapoo

保護されたリソースへのアクセス

クライアントが保護されたリソースへのアクセスを行うのに 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
yapooyapoo

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. どのような条件で DPoP-Nonce を発行するかについては、本仕様では定義されない ↩︎

  2. JWT の exp で有効期限を短くする場合、認可サーバー側で有効期限をコントロールできないという問題がある ↩︎

yapooyapoo

認可コードの公開鍵との紐づけ[1]

同様の仕組みを用いて認可コードを公開鍵に紐づける事もできる。

クライアントはあらかじめ用いたい公開鍵の JWK Thumbprint[2] を計算しておき、これを認可エンドポイントのリクエストパラメータ dpop_jkt で認可サーバーに送信する。認可サーバーは発行する認可コードをこの dpop_jkt に紐づけておく。

トークンエンドポイント (grant_typeauthorization_code の場合) では認可サーバーは受け取った DPoP Proof JWT のヘッダの jwk から JWK Thumbprint を計算し、これが認可コードに紐づけられたものと同じであることを確認する。

認可サーバーが Pushed Authorization Request[3] をサポートする場合、Pushed Authorization Request Endpoint では dpop_jkt を受け付けるのに加え、DPoP リクエストヘッダでの DPoP Proof JWT の指定もサポートしなければならない。

脚注
  1. 正直なところこの仕様の意義はよくわかっておらず、PKCE を使えばよいのではないかと思っている。 ↩︎

  2. RFC 7638 JSON Web Key (JWK) Thumbprint ↩︎

  3. RFC 9126 OAuth 2.0 Pushed Authorization Requests ↩︎