OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP) メモ

* Demonstrating Proof-of-Possession at the Application Layer (DPoP)について個人用にメモする。

### 概要

* アクセストークン漏洩による不正なアクセストークン利用に対するセキュリティ向上策
* ①トークリクエスト/レスポンスと②アクセストークンを用いたリソースアクセス時に次の処理を行う。



![dpop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/9862db7c-97ef-8cba-79f5-f488f1658d96.png)


### ①トークンリクエスト/レスポンス

#### クライアント側

* HTTPヘッダー`DPoP`に`DPoP Proof`を付与して、トークンリクエストを行う。

* **リクエスト例**

  ```http
   POST /token HTTP/1.1
     Host: server.example.com
     Content-Type: application/x-www-form-urlencoded;charset=UTF-8
     DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCRnMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JEQSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwiaWF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg
  
     grant_type=authorization_code
     &code=SplxlOBeZQQYbYS6WxSbIA
     &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
     &code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-
  ```

* `DPoP Proof`:クライアント側で生成した署名情報を含むJWT。

  1. 署名鍵（公開鍵、秘密鍵）を生成する。
  2. 下記形式のヘッダー、ペイロードを用意する。
  3. 2を秘密鍵で署名し、JWTとする。

* **形式**

  ```json
   {
       "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
  }
  ```

  * **ヘッダー部**

    | クレーム | 説明                                     |
    | :------- | :--------------------------------------- |
    | `typ`    | 必須。 `dpop+jwt`を指定する。            |
    | `alg`    | 必須。署名アルゴリズムを指定する。       |
    | `jwk`    | 必須。クライアントが指定した公開鍵情報。 |

  * **ペイロード部**

    | クレーム | 説明                                                        |
    | :------- | :---------------------------------------------------------- |
    | `jti`    | 必須。一意な識別子を指定する。                              |
    | `htm`    | 必須。HTTP メソッドを指定する。※トークンリクエストに対応    |
    | `htu`    | 必須。リクエストの URLを指定する。※トークンリクエストに対応 |
    | `iat`    | 必須。JWT 発行日時を指定する。                              |

#### **認可サーバー**

* トークンリクエストに指定された`DPoP Proof`から公開鍵を取り出し、署名検証を行う。
  * クライアントが秘密鍵を保持していることを確認。
* アクセストークンを生成し、公開鍵と紐づける。
* トークンレスポンスを返却する。

* **レスポンス例**

````http
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`を生成し、②で取得したアクセストークンと併せてAPIコール時に指定する。

  * `DPoP Proof`

    1. ①で生成した公開鍵を含む JSON と、API コールに対応するペイロードを作成

    2. 秘密鍵で署名し、JWTとする。

```http
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 Proof形式

```json
{
     "typ":"dpop+jwt",
     "alg":"ES256",
     "jwk": {
       "kty":"EC",
       "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
       "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
       "crv":"P-256"
     }
}
.
{
     "jti":"e1j3V_bKic8-LAEB",
     "htm":"GET",
     "htu":"https://resource.example.org/protectedresource",
     "iat":1562262618,
     "ath":"fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
}
```



#### リソースサーバー側

* `DPoP proof JWT `に含まれる公開鍵を用いて、署名を検証する。
  * クライアントが公開鍵に対応する秘密鍵を保持していることを確認する。
* `DPoP proof JWT`に含まれる公開鍵と②でアクセストークンに紐付づけた公開鍵が同一か確認する。
  * 同一である場合：API コールを許可
  * 同一でない場合： API コールを拒否

## 参考情報

* [OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer  (DPoP) draft-ietf-oauth-dpop-07](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop)