DPoPの役割
OAuth2において、Bearerトークンはシンプルである一方でアクセストークンさえ取得できれば第三者でも利用できてしまいます。
そこで、アクセストークンを特定のクライアントの公開鍵に紐づけることで、秘密鍵を持つクライアントだけがアクセストークンを利用できるようにした仕組みがDPoPです。
クライアントは秘密鍵・公開鍵ペアを生成し、各HTTPリクエストごとに秘密鍵で署名したDPoP Proof JWTを送信します。
サーバーは公開鍵を用いて署名を検証することで、リクエストを送信したクライアントが秘密鍵を保持していることを確認します。
DPoPのフロー
主要なフローを以下に示します。
DPoP proof JWTについて
DPoP Proof JWTは、リクエストを送信したクライアントが秘密鍵を保持していることを証明するために使用されます。通常、HTTPのDPoPヘッダーに付与されることで利用されます。
DPoP Proof JWTとはJWTの名の通りヘッダー.ペイロード.署名からなる文字列です。(JWTの仕様の記述は割愛します。)
JWTの署名部分はクライアントの秘密鍵によって作られます。ペアの公開鍵を使うことで該当の秘密鍵で署名されたのか検証することができます。
ヘッダー
DPoP Proof JWTのヘッダーが持つクレームの詳細です。
| クレーム | 内容 |
|---|---|
| typ | "dpop+jwt"の固定値 |
| alg | 署名アルゴリズム名 |
| jwk | クライアントの公開鍵 |
RFC9449より抜粋したヘッダー部のサンプルです。
{
"typ":"dpop+jwt",
"alg":"ES256",
"jwk": {
"kty":"EC",
"x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
"y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
"crv":"P-256"
}
}
ペイロード
DPoP Proof JWTのペイロードが持つクレームの詳細です。
| クレーム | 内容 |
|---|---|
| jti | JWT 一意識別子 |
| htm | リクエストのHTTPメソッド |
| htu | リクエストのURL |
| iat | JWT発行日時 |
| ath | アクセストークンのSHA256ハッシュ(リソースアクセス時) |
RFC9449より抜粋したペイロード部のサンプルです。
{
"jti":"-BwC3ESc6acc2lTc",
"htm":"POST",
"htu":"https://server.example.com/token",
"iat":1562262616
}
トークンリクエスト
トークンリクエストのサンプルです。
DPoPヘッダーにDPoP Proof of JWTを付与しています。
これによって秘密鍵のペアとなる公開鍵をトークンエンドポイントに渡しています。
トークンエンドポイントではこの時に公開鍵とアクセストークンの紐づけをしています。
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj
oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia
WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg
4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-
トークンレスポンス
トークンレスポンスのサンプルです。
目立った箇所はないですがtoken_typeの値が(Bearerではなく)DPoPになっています。
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"
}
リソースアクセスのリクエスト
リソースアクセスのリクエストにおいては、
- Authorizaitonヘッダの
DPoP <アクセストークン>の記述 - DPoPヘッダの
<DPoP proof JWT>の記述- また、このJWTはアクセストークンのSHA-256ハッシュ値をathクレームとして含む必要がある
が必須です。
リクエストのサンプルです。
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 JWTの署名が正しいか?
- DPoP proof JWTの公開鍵がアクセストークンに紐づいた公開鍵と同一であるか?
をチェックしています。
まとめ
DPoPは、OAuth2におけるアクセストークンの盗用リスクを軽減するための仕組みです。
Bearerトークンではアクセストークンを取得した第三者でもAPIを利用できてしまいますが、
DPoPではアクセストークンをクライアントの公開鍵に紐づけることで、この問題を解決します。
クライアントは秘密鍵・公開鍵ペアを生成し、リクエスト毎に秘密鍵で署名したDPoP Proof JWTを送信します。
サーバーは公開鍵を用いて署名を検証し、アクセストークンにバインドされた公開鍵との一致を確認することで、
リクエスト発行者が正当なクライアントであるかを確認します。