はじめに
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 Possession(PoP)の仕組みを用いると、アクセストークンを提示しているクライアントがそのアクセストークンの正当な所有者かどうか(=アクセストークンの発行を受けたクライアントと同一かどうか)もチェックされるようになり、アクセストークンの正当な所有者とみなされない場合は API アクセスが拒否されるようになります。
PoP の方法として RFC 8705 の Section 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 の説明』を参照してください。
**(1)**クライアント側で公開鍵と秘密鍵のペアを作成します。
**(2)**作成した公開鍵を含む JSON(後ほど生成する JWT のヘッダーとして利用)と、のちほど投げるトークンリクエストに対応するペイロード(ただしトークンリクエストのペイロードそのものではない)を用意します。
(3)(2)で用意したデータを入力とし、秘密鍵を用いて署名を生成します。
**(4)**これらを一つにして JWT とします。この JWT を『DPoP proof JWT』と呼びます。
**(5)**トークンリクエストをおこないます。この時、先ほど生成した DPoP proof JWT を含めます。
**(6)**トークンエンドポイントの実装は、リクエストから DPoP proof JWT を取り出します。
**(7)**DPoP proof JWT に含まれている公開鍵を用いて署名を検証します。この検証により、クライアントが当該公開鍵に対応する秘密鍵を持っていることを確認できます。
**(9)**生成したアクセストークンと公開鍵を紐付け、その関係を覚えておきます。紐付け方法の詳細は後ほど説明します。
**(11)**クライアントはアクセストークンを用いて API コールをおこないますが、その前に一手間かかります。(1)で作成した公開鍵を含む JSON と、API コールに対応するペイロード(ただし API コールのペイロードそのものではない)を用意します。
(12)(11)で用意したデータを入力とし、秘密鍵を用いて署名を生成します。
**(13)**これらを一つにして JWT とします。この JWT も DPoP proof JWT です。
**(14)**API コールをおこないます。この時、アクセストークンに加えて、先ほど生成した DPoP proof JWT も含めます。
**(15)**API の実装は、リクエストからアクセストークンと DPoP proof JWT を取り出します。
**(16)**DPoP proof JWT に含まれている公開鍵を用いて、署名を検証します。この検証により、クライアントが当該公開鍵に対応する秘密鍵を持っていることを確認できます。
(17)****DPoP proof JWT に含まれている公開鍵とアクセストークンに紐付いているべき公開鍵が同一かどうかを確認します。同一であることが確認できれば、API コールをしているクライアントが、アクセストークンの正当な所有者(=アクセストークンの発行を受けたクライアント)であると言えるので、API コールを許可します。一方、同一でなければ API コールを拒否します。
以上です。
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 6749 で POST
と決められているため)、"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 関連のパラメーター群(dpop
、htm
、htu
)を渡すことにより、DPoP 関連の検証処理は全て Authlete がやってくれるようになります。つまり、『1. 図解 DPoP』の(17)でおこなう「DPoP proof JWT の署名検証」や「アクセストークンと公開鍵の対応チェック」を、Authlete が行います。このため、Protected Resource エンドポイント群の実装はだいぶ楽になります。
おわりに
PoP の一つである DPoP を紹介しました。SPA など、アプリケーションレベルでの PoP が必要な場合は DPoP の採用を検討してみるとよいでしょう。