24
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

図解 DPoP (OAuth アクセストークンのセキュリティ向上策の一つ)

はじめに

DPoP(ディーポップ)という仕様を紹介します。

OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP)
https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/

この仕様は、悪い人がアクセストークンを盗んだとしても、それだけでは API に対する不正アクセスができないようにするための仕組みです。

従来は、クライアントアプリケーションがアクセストークンを提示して API にアクセスしてきたとき、そのアクセストークンが有効であれば、API アクセスは許可されていました。しかし、DPoP などの Proof of PossessionPoP)の仕組みを用いると、アクセストークンを提示しているクライアントがそのアクセストークンの正当な所有者かどうか(=アクセストークンの発行を受けたクライアントと同一かどうか)もチェックされるようになり、アクセストークンの正当な所有者とみなされない場合は API アクセスが拒否されるようになります。

PoP の方法として RFC 8705Section 3 で定義されている方法(通称 MTLS)が使えるのであればそちらのほうがよいですが、それができない場合、例えば Web ブラウザ内で動くシングルページアプリケーション(SPA)の場合、DPoP が PoP の候補となります。

以下、DPoP を図と文章を用いて説明していきますが、動画のほうがお好みであれば、こちら(↓)をご覧ください。

OAuth & OIDC 勉強会 アクセストークン編 by #authlete - 5. Proof of Possession (6:55〜)
https://www.youtube.com/watch?v=ampSEBW6vKM&feature=youtu.be&list=PLxDcFnLrbxvafUBu2GtX35G-iiktmZhWa&t=415

それでは説明を始めます。

1. 図解 DPoP

クライアント、認可サーバー、リソースサーバーがあります。これらの関係については『一番分かりやすい OAuth の説明』を参照してください。
dpop_00.png

(1)クライアント側で公開鍵と秘密鍵のペアを作成します。
dpop_01.png

(2)作成した公開鍵を含む JSON(後ほど生成する JWT のヘッダーとして利用)と、のちほど投げるトークンリクエストに対応するペイロード(ただしトークンリクエストのペイロードそのものではない)を用意します。
dpop_02.png

(3)(2)で用意したデータを入力とし、秘密鍵を用いて署名を生成します。
dpop_03.png

(4)これらを一つにして JWT とします。この JWT を『DPoP proof JWT』と呼びます。
dpop_04.png

(5)トークンリクエストをおこないます。この時、先ほど生成した DPoP proof JWT を含めます。
dpop_05.png

(6)トークンエンドポイントの実装は、リクエストから DPoP proof JWT を取り出します。
dpop_06.png

(7)DPoP proof JWT に含まれている公開鍵を用いて署名を検証します。この検証により、クライアントが当該公開鍵に対応する秘密鍵を持っていることを確認できます。
dpop_07.png

(8)アクセストークンを生成します。
dpop_08.png

(9)生成したアクセストークンと公開鍵を紐付け、その関係を覚えておきます。紐付け方法の詳細は後ほど説明します。
dpop_09.png

(10)アクセストークンを発行します。
dpop_10.png

(11)クライアントはアクセストークンを用いて API コールをおこないますが、その前に一手間かかります。(1)で作成した公開鍵を含む JSON と、API コールに対応するペイロード(ただし API コールのペイロードそのものではない)を用意します。
dpop_11.png

(12)(11)で用意したデータを入力とし、秘密鍵を用いて署名を生成します。
dpop_12.png

(13)これらを一つにして JWT とします。この JWT も DPoP proof JWT です。
dpop_13.png

(14)API コールをおこないます。この時、アクセストークンに加えて、先ほど生成した DPoP proof JWT も含めます。
dpop_14.png

(15)API の実装は、リクエストからアクセストークンと DPoP proof JWT を取り出します。
dpop_15.png

(16)DPoP proof JWT に含まれている公開鍵を用いて、署名を検証します。この検証により、クライアントが当該公開鍵に対応する秘密鍵を持っていることを確認できます。
dpop_16.png

(17)DPoP proof JWT に含まれている公開鍵とアクセストークンに紐付いているべき公開鍵が同一かどうかを確認します。同一であることが確認できれば、API コールをしているクライアントが、アクセストークンの正当な所有者(=アクセストークンの発行を受けたクライアント)であると言えるので、API コールを許可します。一方、同一でなければ API コールを拒否します。
dpop_17.png

以上です。

フルカラーの図
dpop_18.png

2. アクセストークンと公開鍵の紐付け方法

アクセストークンと公開鍵の紐付けは、具体的には、公開鍵のハッシュ値をアクセストークンの属性の一つとして覚えておくことでおこないます。このハッシュ値は、RFC 7638 で定義されている JWK SHA-256 Thumbprint です。

アクセストークンが公開鍵と紐付いている場合、イントロスペクション API(RFC 7662)が返すレスポンスには、その公開鍵のハッシュ値が含まれます。具体的には、公開鍵の JWK SHA-256 Thumbprint を BASE64URL でエンコードした値が、cnf クレームの下の jkt クレームの値として含まれます。

"cnf": {
  "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
}

アクセストークンのフォーマットが JWT の場合、そのペイロード部分には、同様にして cnf クレームの下の jkt クレームの値として、公開鍵のハッシュ値が含まれます。

3. DPoP proof JWT

3.1 DPoP proof JWT ヘッダー

DPoP proof JWT のヘッダー部分の仕様は次の通りです。

クレーム 要否 説明
typ 必須 固定値 "dpop+jwt"
alg 必須 署名アルゴリズム。RFC 7518, Section 3.1 の表内の識別子。ただし "none" 及び対称鍵系アルゴリズムは不可。
jwk 必須 クライアントが選択した公開鍵

次のものは、DPoP の仕様書の Section 4 から抜粋した、DPoP proof JWT のヘッダーの例です。

{
  "typ":"dpop+jwt",
  "alg":"ES256",
  "jwk": {
    "kty":"EC",
    "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
    "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
    "crv":"P-256"
  }
}

3.2. DPoP proof JWT ペイロード

DPoP proof JWT のペイロード部分の仕様は次の通りです。

クレーム 要否 説明
jti 必須 JWT 一意識別子
htm 必須 リクエストの HTTP メソッド
htu 必須 リクエストの URL
iat 必須 JWT 発行日時

次のものは、DPoP の仕様書の Section 4 から抜粋した、DPoP proof JWT のペイロードの例です。

{
  "jti":"-BwC3ESc6acc2lTc",
  "htm":"POST",
  "htu":"https://server.example.com/token",
  "iat":1562262616
}

例えばトークンリクエスト用の DPoP proof JWT の場合、"htm" は常に "POST" になり(RFC 6749POST と決められているため)、"htu" はトークンエンドポイントの URL になります。

4. トークンリクエスト

先に説明したとおり、公開鍵に紐付いたアクセストークンを生成してもらいたい場合、トークンリクエストに DPoP proof JWT を含めます。その含め方ですが、DPoP という HTTP ヘッダーの値として DPoP proof JWT を設定します。

下記は、DPoP の仕様書の Section 5 から抜粋したトークンリクエストの例です。

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-

認可サーバーが DPoP をサポートしている場合、DPoP proof JWT に含まれる公開鍵と生成されるアクセストークンが紐付けられます。そして、トークンレスポンスに含まれる "token_type" の値が "DPoP" となります。

逆に、認可サーバーが DPoP をサポートしていない場合、DPoP ヘッダーは単に無視され、公開鍵とアクセストークンとの紐付けは行われません。この場合、トークンレスポンスの "token_type" の値は "DPoP" 以外の値、典型例としては "Bearer"、になります。

5. API コール

API コールへの DPoP proof JWT の含め方は、トークンリクエストの場合と同様、DPoP HTTP ヘッダーを使います。

一方、少し変わるのは、アクセストークンを渡す手段として Authorization ヘッダーを使う場合です。

RFC 6750, Section 2.1 に則った従来の方法では、次のようにしてアクセストークンを渡します。

Authorization: Bearer アクセストークン

一方、アクセストークンが公開鍵に紐付いている場合は、Authorization ヘッダーを使う場合、次のように Bearer ではなく DPoP を使います。

Authorization: DPoP アクセストークン

下記は、DPoP の仕様書の Section 6 から抜粋した API コールの例です。

GET /protectedresource HTTP/1.1
Host: resource.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWI
 iOiJzb21lb25lQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbX
 BsZS5jb20iLCJhdWQiOiJodHRwczovL3Jlc291cmNlLmV4YW1wbGUub3JnIiwibmJmI
 joxNTYyMjYyNjExLCJleHAiOjE1NjIyNjYyMTYsImNuZiI6eyJqa3QiOiIwWmNPQ09S
 Wk5ZeS1EV3BxcTMwalp5SkdIVE4wZDJIZ2xCVjN1aWd1QTRJIn19.vsFiVqHCyIkBYu
 50c69bmPJsj8qYlsXfuC6nZcLl8YYRNOhqMuRXu6oSZHe2dGZY0ODNaGg1cg-kVigzY
 hF1MQ
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIj
 oiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0Z
 WRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOH0.lNhmpAX1WwmpBvwhok4E74kWCiGB
 NdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg1JSskfWIyo4UCbQ

6. Authlete

Authlete(オースリート)はバージョン 2.2 以降で DPoP をサポートします。DPoP をサポートするため、幾つかの Authlete API に DPoP 用のパラメーター群が追加されました。参考までに、追加されたパラメーター群を紹介します。

/api/auth/token API

パラメーター 説明
dpop トークンリクエストに含まれている DPoP HTTP ヘッダーの値。
htm トークンリクエストの HTTP メソッドの値。デフォルト値は POST
htu トークンエンドポイントの URL。デフォルト値はサービスの tokenEndpoint

トークンリクエストに含まれる DPoP ヘッダーの値を dpop として /api/auth/token API に渡すと、アクセストークンと公開鍵の紐付け処理は Authlete がやってくれます。これだけで DPoP に対応したアクセストークンが生成されます。

/api/auth/userinfo API

パラメーター 説明
dpop ユーザー情報リクエストに含まれている DPoP HTTP ヘッダーの値
htm ユーザー情報リクエストの HTTP メソッドの値。通常は GET もしくは POST
htu ユーザー情報エンドポイントの URL。デフォルト値はサービスの userInfoEndpoint

ユーザー情報リクエストに含まれる DPoP ヘッダーの値を dpop として /api/auth/userinfo API に渡すと、DPoP に必要な検証処理を Authlete がやってくれます。

/api/auth/introspection API

パラメーター 説明
dpop Protected Resource エンドポイントへのリクエストに含まれている DPoP HTTP ヘッダーの値
htm Protected Resource エンドポイントへのリクエストの HTTP メソッドの値
htu Protected Resource エンドポイントの URL。

Authlete の /api/auth/introspection API は、RFC 7662 が定めるものとは異なる、Authlete 独自のイントロスペクション API です。Authlete のイントロスペクション API は、アクセストークンの情報を返すだけでなく、様々な検証処理をおこない、必要に応じて RFC 6750 に準拠するエラーメッセージの構築もおこないます。この Authlete イントロスペクション API に DPoP 関連のパラメーター群(dpophtmhtu)を渡すことにより、DPoP 関連の検証処理は全て Authlete がやってくれるようになります。つまり、『1. 図解 DPoP』の(17)でおこなう「DPoP proof JWT の署名検証」や「アクセストークンと公開鍵の対応チェック」を、Authlete が行います。このため、Protected Resource エンドポイント群の実装はだいぶ楽になります。

おわりに

PoP の一つである DPoP を紹介しました。SPA など、アプリケーションレベルでの PoP が必要な場合は DPoP の採用を検討してみるとよいでしょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
24
Help us understand the problem. What are the problem?