ritou です。
みんな大好きJWT。今日もみんなで eyJ!ということで、今回はこちらの仕様について説明します。
概要
This document defines a new JWT-based mode to encode authorization responses. Clients are enabled to request the transmission of the authorization response parameters along with additional data in JWT format. This mechanism enhances the security of the standard authorization response since it adds support for signing and encryption, sender authentication, audience restriction as well as protection from replay, credential leakage, and mix-up attacks. It can be combined with any response type.
この仕様を3行で説明しろと言われたらこんな感じでしょう。
- JWT形式で Authorization Response を返すための仕様です
- Response Mode を指定するための
response_mode
パラメータも拡張する - Client / Authorization Server それぞれの metadata も拡張する
まずは Response Mode の振り返りからはじめましょう。
これまでの Response Mode とは
response_mode
パラメータが定義されているのは、この仕様です。
Response Mode
The Response Mode determines how the Authorization Server returns result parameters from the Authorization Endpoint. Non-default modes are specified using the response_mode request parameter. If response_mode is not present in a request, the default Response Mode mechanism specified by the Response Type is used.
Response Mode は Authorization Server が(認可処理の)結果のパラメータを Authorization Endpoint から「どのように返すか」を決定します。
デフォルト値ではないmodeは response_mode
というリクエストパラメータを用いて指定され、指定されない場合は Response Type に対応した デフォルトの Response Mode が利用されます。
OAuth 2.0 の Response Type で表現すると
response_type=code
: いわゆる Authorization Code Grant であり、Authorization Response はクエリパラメータとして指定される -> デフォルトの Response Mode はquery
response_type=fragment
: いわゆる Implicit Grant であり、Authorization Response はフラグメント部分に含まれる -> デフォルトの Response Mode はfragment
という感じになります。
また、こちらの仕様により response_mode
パラメータに form_post
が追加されました。
例を見て見ましょう。まず Authorization Request に response_mode
パラメータが指定されます。
GET /authorize? response_type=id_token &response_mode=form_post &client_id=some_client &scope=openid &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcallback &state=DcP7csa3hMlvybERqcieLHrRzKBra &nonce=2T1AgaeRTGTMAJyeDMN9IJbgiUG HTTP/1.1 Host: server.example.com
Authorization Response は HTML Form を用いた POST リクエストにより返されます。
HTTP/1.1 200 OK Content-Type: text/html;charset=UTF-8 Cache-Control: no-cache, no-store Pragma: no-cache <html> <head><title>Submit This Form</title></head> <body onload="javascript:document.forms[0].submit()"> <form method="post" action="https://client.example.org/callback"> <input type="hidden" name="state" value="DcP7csa3hMlvybERqcieLHrRzKBra"/> <input type="hidden" name="id_token" value="eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJqb2huIiw iYXVkIjoiZmZzMiIsImp0aSI6ImhwQUI3RDBNbEo0c2YzVFR2cllxUkIiLC Jpc3MiOiJodHRwczpcL1wvbG9jYWxob3N0OjkwMzEiLCJpYXQiOjEzNjM5M DMxMTMsImV4cCI6MTM2MzkwMzcxMywibm9uY2UiOiIyVDFBZ2FlUlRHVE1B SnllRE1OOUlKYmdpVUciLCJhY3IiOiJ1cm46b2FzaXM6bmFtZXM6dGM6U0F NTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZCIsImF1dGhfdGltZSI6MTM2Mz kwMDg5NH0.c9emvFayy-YJnO0kxUNQqeAoYu7sjlyulRSNrru1ySZs2qwqq wwq-Qk7LFd3iGYeUWrfjZkmyXeKKs_OtZ2tI2QQqJpcfrpAuiNuEHII-_fk IufbGNT_rfHUcY3tGGKxcvZO9uvgKgX9Vs1v04UaCOUfxRjSVlumE6fWGcq XVEKhtPadj1elk3r4zkoNt9vjUQt9NGdm1OvaZ2ONprCErBbXf1eJb4NW_h nrQ5IKXuNsQ1g9ccT5DMtZSwgDFwsHMDWMPFGax5Lw6ogjwJ4AQDrhzNCFc 0uVAwBBb772-86HpAkGWAKOK-wTC6ErRTcESRdNRe0iKb47XRXaoz5acA"/> </form> </body> </html>
最終的に、Client に送られるのは以下のようなPOSTリクエストになります。
POST /callback HTTP/1.1 Host: client.example.org Content-Type: application/x-www-form-urlencoded id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJqb2huIiwiYX VkIjoiZmZzMiIsImp0aSI6ImhwQUI3RDBNbEo0c2YzVFR2cllxUkIiLCJpc 3MiOiJodHRwczpcL1wvbG9jYWxob3N0OjkwMzEiLCJpYXQiOjEzNjM5MDMx MTMsImV4cCI6MTM2MzkwMzcxMywibm9uY2UiOiIyVDFBZ2FlUlRHVE1BSnl lRE1OOUlKYmdpVUciLCJhY3IiOiJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTD oyLjA6YWM6Y2xhc3NlczpQYXNzd29yZCIsImF1dGhfdGltZSI6MTM2MzkwM Dg5NH0.c9emvFayy-YJnO0kxUNQqeAoYu7sjlyulRSNrru1ySZs2qwqqwwq -Qk7LFd3iGYeUWrfjZkmyXeKKs_OtZ2tI2QQqJpcfrpAuiNuEHII-_fkIuf bGNT_rfHUcY3tGGKxcvZO9uvgKgX9Vs1v04UaCOUfxRjSVlumE6fWGcqXVE KhtPadj1elk3r4zkoNt9vjUQt9NGdm1OvaZ2ONprCErBbXf1eJb4NW_hnrQ 5IKXuNsQ1g9ccT5DMtZSwgDFwsHMDWMPFGax5Lw6ogjwJ4AQDrhzNCFc0uV AwBBb772-86HpAkGWAKOK-wTC6ErRTcESRdNRe0iKb47XRXaoz5acA& state=DcP7csa3hMlvybERqcieLHrRzKBra
Response Mode についてなんとなくわかりましたでしょうか。 今回の仕様では、この辺りをさらに拡張するぞ!って話です。
JWT形式で Authorization Response を返すための Response Mode
仕様では Authorization Response に含まれる JWT の内容から先に定義されていますが、Response Mode のあたりから説明していきます。
この仕様で定義されている response_mode
パラメータは次の4つです。
query.jwt
: JWT形式の Authorization Response をクエリパラメータに指定するfragment.jwt
: JWT形式の Authorization Response をフラグメントに指定するform_post.jwt
: JWT形式の Authorization Response を HTTP POST で送るjwt
: Response Type に対応した Response Mode にて JWT形式の Authorization Response を返すresponse_mode=jwt&response_type=code
:response_mode=query.jwt
response_mode=jwt&response_type=(code, none以外)
:response_mode=fragment.jwt
Authorization Response の例の前に、response_type
パラメータ毎にどのようなJWTになるかを見ていきましょう。
JWT Response Document
JWTは以下のデータを必ず含みます。
iss
: レスポンスを作成した Authorization Server の Issuer URLaud
: レスポンスの対象である Client のclient_id
exp
: JWT の有効期限
さらに、JWTはエラーレスポンスの場合でも Authorization Endpoint のレスポンスパラメータを含みます。
複数の Response Type の組み合わせに対しても適用されますが、この仕様では code
, token
について示します。
OIDC の Session Management のような拡張仕様で定義されているレスポンスパラメータもJWTに含まれます。
Response Type が "code" である時の Authorization Response
いわゆる Authorization Code Grant のレスポンスが JWT に含まれます。
code
: Authorization Codestate
: リクエストに含まれるstate
パラメータ
JWT の Payload は以下のようになります。
{ "iss":"https://accounts.example.com", "aud":"s6BhdRkqt3", "exp":1311281970, "code":"PyyFaux2o7Q0YfXBU32jhw.5FXSQpvr8akv9CeRDSd0QA", "state":"S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw" }
エラーレスポンスの時は JWT に RFC6749 の sections 4.1.2.1 で定義されているエラーレスポンスパラメータを含みます。
error
error_description
(OPTIONAL) - 人間が読めるようなエラー内容error_uri
(OPTIONAL) - エラーの内容についてのURLstate
JWT の Payload は以下のようになります。
{ "error":"access_denied", "state":"S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw" }
エラーレスポンスについては Client に戻すべきエラーとそうでないものがあるので、その辺りは今まで通り気をつける必要がありそうです。
Response Type が "token" である時の Authorization Response
Implicit Grant の場合は次のようになります。
access_token
- Access Tokenの値token_type
- Access Token の種類。 "Bearer" などexpires_in
- Access Token の有効期限scope
- Access Token の Scopestate
RFC6749 と照らし合わせると、Scope は オプションですかね。
{ "iss":"https://accounts.example.com", "aud":"s6BhdRkqt3", "exp":1311281970, "access_token":"2YotnFZFEjr1zCsicMWpAA", "state":"S8NJ7uqk5fY4EjNvP_G_FtyJu6pUsvH9jsYni9dMAJw", "token_type":"bearer", "expires_in":"3600", "scope":"example" }
エラーレスポンスについては code
の場合と同様です。
Signing and Encryption
上記のレスポンスパラメータはJWS で署名をつける もしくは JWE にて暗号化されます。 Authorization Server は 自分自身とClient の metadata を用いてアルゴリズムを決定します。
次は Response Mode 毎の Authorization Response を見ていきましょう。
Response Mode "query.jwt"
response
というクエリパラメータとしてJWTの値が指定され、HTTP リダイレクトにより Client に送られます。
フォーマットは application/x-www-form-urlencoded
です。
HTTP/1.1 302 Found Location: https://client.example.com/cb? response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLm V4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTMxMTI4MTk3MCwiY29kZSI6IlB5eU ZhdXgybzdRMFlmWEJVMzJqaHcuNUZYU1FwdnI4YWt2OUNlUkRTZDBRQSIsInN0YXRlIjoiUzhOSjd1cW s1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkTUFKdyJ9.HkdJ_TYgwBBj10C-aWuNUiA062Amq 2b0_oyuc5P0aMTQphAqC2o9WbGSkpfuHVBowlb-zJ15tBvXDIABL_t83q6ajvjtq_pqsByiRK2dLVdUw KhW3P_9wjvI0K20gdoTNbNlP9Z41mhart4BqraIoI8e-L_EfAHfhCG_DDDv7Yg
response_type
に token
, id_token
が含まれ、 JWE を"使わない"時、 URL からの漏洩を防ぐために query.jwt
の値を使ってはいけません。
Response Mode "fragment.jwt"
フラグメント部に response
というパラメータとしてJWTの値が指定され、HTTP リダイレクトにより Client に送られます。
フォーマットは application/x-www-form-urlencoded
です。
HTTP/1.1 302 Found Location: https://client.example.com/cb# response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLm V4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTMxMTI4MTk3MCwiYWNjZXNzX3Rva2 VuIjoiMllvdG5GWkZFanIxekNzaWNNV3BBQSIsInN0YXRlIjoiUzhOSjd1cWs1Zlk0RWpOdlBfR19GdH lKdTZwVXN2SDlqc1luaTlkTUFKdyIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJleHBpcmVzX2luIjoiMz YwMCIsInNjb3BlIjoiZXhhbXBsZSJ9.bgHLOu2dlDjtCnvTLK7hTN_JNwoZXEBnbXQx5vd9z17v1Hyzf Mqz00Vi002T-SWf2JEs3IVSvAe1xWLIY0TeuaiegklJx_gvB59SQIhXX2ifzRmqPoDdmJGaWZ3tnRyFW NnEogJDqGFCo2RHtk8fXkE5IEiBD0g-tN0GS_XnxlE
Response Mode "form_post.jwt"
response
というHTML フォームの値としてJWTの値が指定され、User-Agent内で自動的に送信されることで HTTP POSTメソッドにより Client に送られます。
フォーマットは application/x-www-form-urlencoded
です。
HTTP/1.1 200 OK Content-Type: text/html;charset=UTF-8 Cache-Control: no-cache, no-store Pragma: no-cache <html> <head><title>Submit This Form</title></head> <body onload="javascript:document.forms[0].submit()"> <form method="post" action="https://client.example.com/cb"> <input type="hidden" name="response" value="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2 FjY291bnRzLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTM xMTI4MTk3MCwiYWNjZXNzX3Rva2VuIjoiMllvdG5GWkZFanIxekNzaWNNV3BBQSIs InN0YXRlIjoiUzhOSjd1cWs1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkT UFKdyIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJleHBpcmVzX2luIjoiMzYwMCIsIn Njb3BlIjoiZXhhbXBsZSJ9.bgHLOu2dlDjtCnvTLK7hTN_JNwoZXEBnbXQx5vd9z1 7v1HyzfMqz00Vi002T-SWf2JEs3IVSvAe1xWLIY0TeuaiegklJx_gvB59SQIhXX2i fzRmqPoDdmJGaWZ3tnRyFWNnEogJDqGFCo2RHtk8fXkE5IEiBD0g-tN0GS_XnxlE"/> </form> </body> </html>
Client には HTTP POSTなリクエストが送られます。
POST /cb HTTP/1.1 Host: client.example.org Content-Type: application/x-www-form-urlencoded response=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2 FjY291bnRzLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsImV4cCI6MTM xMTI4MTk3MCwiYWNjZXNzX3Rva2VuIjoiMllvdG5GWkZFanIxekNzaWNNV3BBQSIs InN0YXRlIjoiUzhOSjd1cWs1Zlk0RWpOdlBfR19GdHlKdTZwVXN2SDlqc1luaTlkT UFKdyIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJleHBpcmVzX2luIjoiMzYwMCIsIn Njb3BlIjoiZXhhbXBsZSJ9.bgHLOu2dlDjtCnvTLK7hTN_JNwoZXEBnbXQx5vd9z1 7v1HyzfMqz00Vi002T-SWf2JEs3IVSvAe1xWLIY0TeuaiegklJx_gvB59SQIhXX2i fzRmqPoDdmJGaWZ3tnRyFWNnEogJDqGFCo2RHtk8fXkE5IEiBD0g-tN0GS_XnxlE
Processing rules
Authorization Response を受け取った Client 側の検証処理について説明します。
- (OPTIONAL) JWT ヘッダパラメータの
kid
の値を用いてJWTをdecryptする state
パラメータを取得して User-Agent との紐付けを検証(CSRF対策)state
パラメータはワンタイムなCSRF対策トークンとして利用され、検証完了後破棄されます。iss
の値を検証 (期待したものと一致するかどうか)aud
の値を検証 (client_id
と一致するかどうか)exp
の値を検証iss
,kid
の組み合わせから署名を検証. 鍵周りの詳細はこの仕様の範囲外。
いずれの検証も失敗したらすぐにエラーを返します。
これらの検証が終わるまでは、Authorization Response の中身である code
や access_token
などの値を利用してはいけません。
Metadata
Client Metadata
Client 側で期待するJWTの署名や暗号化のアルゴリズムなどを定義できます。詳細は仕様みてください。
authorization_signed_response_alg
authorization_encrypted_response_alg
authorization_encrypted_response_enc
Authorization Server Metadata
Authozation Server 側でサポートしている値を定義できます。詳細は仕様みてください。
authorization_signing_alg_values_supported
authorization_encryption_alg_values_supported
authorization_encryption_enc_values_supported
response_modes_supported
: サポートするresponse_mode
パラメータ
OAuth Server Metadata についてはこちらのRFCを参照してください。
RFC 8414 - OAuth 2.0 Authorization Server Metadata
Security considerations
- DoS using specially crafted JWTs : でかい JWK Set を処理する感じになってネットワークやCPUを圧迫するとか、JWK Set URL が DDos のターゲットみたいになるとか。鍵周りはよく考えて設計する必要がありますね。
- Code Replay : 同一 Client, 別のユーザー向けの Authorization Code を含む攻撃は、
state
パラメータの検証で対応可能です。 - Mix-Up : あるClientと複数のAuthorization Serverがやり取りする場合のMix-Up 攻撃については 図解:OAuth 2.0に潜む「5つの脆弱性」と解決法 (4/4):デジタルID最新動向(2) - @IT 見れば良いと思います。
iss
,aud
の検証をしっかりやる必要があります。 - Code Leakage : 暗号化すれば
code
の値も外から見えなくなります。
まとめ
- Authorization Response に JWT を使う方法が定義されている
- Response Type, Response Mode による挙動とJWTの中身の違いに注目せよ
- JWT生成のところで鍵周りとかは仕様範囲外なので、よく考えて設計する必要がある
というところでしょうか。
もっと FAPI について知りたい?
ひたすら仕様を読む会ってのがあるみたいです。
今回紹介した JARM も入ってますが、既にあなたは「JARM完全に理解した」状態なので余裕ですね。 お時間とか色々と余裕がある方は是非参加してみてください。
ところで、"JARM" ってどう読むの??? ではまた。