JAR (JWT-Secured Authorization Request) に関する実装者の覚書

# はじめに

この記事は、**JAR** と呼ばれる技術仕様『**[RFC 9101 The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR)](https://www.rfc-editor.org/rfc/rfc9101.html)**』に関する実装者の覚書です。(英語版は[こちら](https://darutk.medium.com/implementers-note-about-jar-fff4cbd158fe))

**[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)** (**OIDC Core**) と JAR の間には幾つか衝突する部分があります。既存の認可サーバーだけでなく OpenID 互換性検査スイートにさえも影響を及ぼすため、OAuth コミュニティーはそれらの破壊的変更について長い間論争を続けました。

衝突する部分とは何か？それらはどのように解決されたのか？ JAR 仕様の未来の読者や実装者のため、この記事はそれらについて記述します。

# JAR とは？

JAR とは、認可リクエストパラメーター群を一つの署名付き JWT にまとめるための仕組みです。クライアントアプリケーションは、リクエストパラメーター群を個々に列挙する代わりに、その JWT を (ユーザーエージェントを介して) サーバーに送ります。

その JWT は『**リクエストオブジェクト**』と呼ばれます。リクエストオブジェクトは `request` パラメーターの値としてサーバーに送られるか、もしくは、その場所が `request_uri` パラメーターの値として送られます。

例えば、7 つの別々のクエリーパラメーターを持つ次の認可リクエストですが、

![authorization-request.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/06bafde7-33b1-ecbf-5c69-485811bc36e1.png)

`request` パラメーターを使うと次のようになります。

![authorization-request+request_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/8b3393ee-2b4f-e09b-9dfd-20e00714bcd0.png)

また、`request_uri` パラメーターを使うと次のようになります。

![authorization-request+request_uri_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/6f3f8d31-7785-124c-74bc-2389aff5ed6f.png)

7 つのリクエストパラメーターは全てリクエストオブジェクトの中に置かれており、また同時に、リクエストオブジェクトの外にも重複して `client_id` パラメーターが置かれています。

JAR の初期ドラフトは、大胆にも `client_id` を落として、認可リクエストは `request` または `request_uri` のいずれかのパラメーターのみを使うようにと要求していました。しかし、認可サーバー実装者の多くが `client_id` は必須のまま残しておくべきだと主張しました。なぜでしょうか？理由は次の通りです。

1. リクエストオブジェクトが対称鍵系アルゴリズムで暗号化されている場合、復号化のために共有秘密鍵が必要です。対称鍵系アルゴリズムでは、クライアントシークレットが鍵として使われるので (OIDC Core [Section 10.1](https://openid.net/specs/openid-connect-core-1_0.html#Signing))、リクエストオブジェクトを復号化する**前に**、認可サーバーはクライアントアプリケーションを特定してデータベースからクライアントシークレットを取り出せなければなりません。暗号化されたリクエストオブジェクト内に `client_id` が含まれているとしても、認可サーバーは復号化の前にその値を知ることはできません。
2. リクエストオブジェクトが `request_uri` で指定されている場合、認可サーバーはその場所にリクエストオブジェクトを取りにいかねばなりません。しかし、セキュリティー上の理由から、認可サーバーは事前登録されていない場所にリクエストオブジェクトを取りにいくことを拒否してもよい (個人的には「すべき」と思っています) とされています (参考：OpenID Connect Discovery 1.0, [Section 3](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata), `require_request_uri_registration`)。提示されたリクエスト URI と当該クライアント用に事前登録されているリクエスト URI 群との照合をおこなうため、リクエストオブジェクトを取りにいく**前に**、認可サーバーはクライアントアプリケーションを特定できなければなりません。離れた場所にあるリクエストオブジェクト内に `client_id` が含まれているとしても、認可サーバーはリクエストオブジェクトを取りにいく前にその値を知ることはできません。

# 衝突箇所

リクエストオブジェクトは 2014 に世に出た OIDC Core の一部 ([Section 6](https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests)) として定義されました。JAR の目的は、リクエストオブジェクトの仕様を OIDC Core から取り出し、汎用的に使えるようにすることです。この作業にはそれほど時間がかからないだろうと関係者は考えていましたが、JAR により導入される破壊的変更について OAuth コミュニティーからの同意を得るまでに、最初のドラフトから実に 6 年以上も経過しました。

## リクエストパラメーター群のマージ

|リクエストパラメーター|OAuth 2.0|OIDC|JAR|
|:--|:-:|:-:|:-:|
|リクエストオブジェクト外の<br>リクエストパラメーター群|マージされる|マージされる|無視される|
|リクエストオブジェクト内の<br>リクエストパラメーター群|マージされる|マージされる|参照される|

OIDC Core では、リクエストオブジェクト内外のリクエストパラメーター群はマージされます。これについては、[Section 6.3.3](https://openid.net/specs/openid-connect-core-1_0.html#RequestParameterValidation) で次のように明確に述べられています。

> The Authorization Server MUST assemble the set of Authorization Request parameters to be used from the Request Object value and the OAuth 2.0 Authorization Request parameters (minus the `request` or `request_uri` parameters). If the same parameter exists both in the Request Object and the OAuth Authorization Request parameters, the parameter in the Request Object is used.

一方、JAR ではリクエストオブジェクト外のリクエストパラメーター群を参照することは禁止されています。これについては、Section 5 で次のように述べられています。

> However, the authorization server supporting this specification MUST only use the parameters included in the request object.

また、Section 6.3 にも次のようにあります。

> The Authorization Server MUST only use the parameters in the Request Object even if the same parameter is provided in the query parameter.

## response_type

|`response_type`|OAuth 2.0|OIDC|JAR|
|:--|:-:|:--|:-:|
|リクエストオブジェクト外の `response_type`|必須|必須|無視|
|リクエストオブジェクト内の `response_type`|任意|任意。存在する場合は、リクエストオブジェクト外の値と一致しなければならない。|必須|

OAuth 2.0 ([RFC 6749](https://tools.ietf.org/html/rfc6749)) と OIDC Core では、`response_type` は必須パラメーターとされています。リクエストオブジェクトが `response_type` を含んでいたとしても、OIDC Core はリクエストオブジェクト外にも `response_type` を要求します。OIDC Core の [Section 6.1](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) に次のような記述があります。

> So that the request is a valid OAuth 2.0 Authorization Request, values for the `response_type` and `client_id` parameters MUST be included using the OAuth 2.0 request syntax, since they are REQUIRED by OAuth 2.0. The values for these parameters MUST match those in the Request Object, if present.

OIDC Core が OAuth 2.0 との互換性を重視していることは明らかです。しかし、JAR は、コミュニティーから反対意見が出たものの、リクエストオブジェクト外の `response_type` を要求しないことに決めました。

結果として、**JAR 規則が適用される場合、OIDC Core に準拠して「リクエストオブジェクト外に `response_type` を持たない OIDC リクエスト」を拒否する認可サーバーは、誤った実装とみなされる**ことになります。

当然、「JAR 規則が適用される場合にも `response_type` に関する OIDC Core の規定は有効とするべき」という提案もありました。しかし、最終的に到達した合意は、"[servers must not require the duplicates](https://bitbucket.org/openid/fapi/issues/315#comment-58929318)" (サーバーは重複を要求してはならない) でした。([FAPI Issue 315](https://bitbucket.org/openid/fapi/issues/315))

**『必須』を『任意または無視』に変更することは、逆方向の変更よりも、実装に対して大きなインパクトがあります。**というのは、「早い段階で行われるパラメーターチェックを通過した後は必須パラメーターは常に利用可能である」という前提のもとに書かれているソースプログラムを見直し、適宜修正する必要があるからです。

## scope

|`scope`|OAuth 2.0|OIDC|JAR|
|:--|:-:|:--|:-:|
|リクエストオブジェクト外の `scope`|任意|必須で、かつ `openid` を含まなければならない。省略された場合、または `openid` を含まない場合、そのリクエストは OIDC リクエストとはみなされない (ただしこれはエラーではない)。もしもリクエストオブジェクトが与えられ、それが `scope` を含み、その `scope` が `openid` を含む場合、リクエストオブジェクト外にも `scope` が必須となり、その `scope` は `openid` を含まなければならない。|無視|
|リクエストオブジェクト内の `scope`|任意|任意。存在し、かつ `openid` を含む場合、リクエストオブジェクト外にも `openid` を含む `scope` が必須となる。|任意|

OAuth 2.0 では `scope` パラメーターは任意ですが、OIDC Core は `scope` パラメーターを必須としました。下記は、OIDC Core の [Section 3.1.2.1](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) から抜粋した `scope` パラメーターに関する説明です。

> REQUIRED. OpenID Connect requests MUST contain the `openid` scope value. If the `openid` scope value is not present, the behavior is entirely unspecified. Other scope values MAY be present. Scope values used that are not understood by an implementation SHOULD be ignored. See Sections 5.4 and 11 for additional scope values defined by this specification.

加えて、[Section 6.1](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) は、リクエストオブジェクトが `scope` を含み、その `scope` が `openid` を含む場合、リクエストオブジェクト外にも重複して `scope` パラメーターを置くことを要求しています。

> Even if a `scope` parameter is present in the Request Object value, a `scope` parameter MUST always be passed using the OAuth 2.0 request syntax containing the `openid` scope value to indicate to the underlying OAuth 2.0 logic that this is an OpenID Connect request.

`response_type` と同じ結論を適用するのであれば、サーバーは `scope` の重複指定を要求してはなりません。関連する議論は OAuth メーリングリストのアーカイブに見つけることができます ([[OAUTH-WG] [JAR] scope parameter outside request object of OIDC request)](https://mailarchive.ietf.org/arch/msg/oauth/1q9C7wbmc92_prDgpsAUFcJlWLM/))。

結果として、**JAR 規則が適用される場合、OIDC Core に準拠して「`openid` を含む `scope` をリクエストオブジェクト外に持たない OIDC リクエスト」を拒否する認可サーバーは、誤った実装とみなされる**ことになります。

## 署名

|署名|OAuth 2.0|OIDC|JAR|
|:--|:-:|:-:|:-:|
|リクエストオブジェクトの署名|任意|任意|必須|

JAR は、リクエストオブジェクトの署名を常に要求します。一方、OIDC Core では署名の無いリクエストオブジェクトも許容されます。OIDC Core の [Section 6.1](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) は次のように述べています。

> The Request Object MAY be signed or unsigned (plaintext)

興味深い副作用があります。もしも認可サーバーが常に JAR 規則を適用するのであれば、ディスカバリードキュメント ([OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)) 内の `request_object_signing_alg_values_supported` から `none` を取り除くべきです。

# FAPI

**FAPI** (**[Financial-grade API](https://fapi.openid.net/)**) は高度な API セキュリティーのための仕様です。FAPI は OIDC を基盤として策定されているので、FAPI の実装も JAR により影響を受けます。

参考までに、[FAPI Part 2](https://openid.net/specs/openid-financial-api-part-2-ID2.html) には次に示すように、リクエストオブジェクトに関係する要求事項が幾つかあります。

- **[FAPI Part 2, Section 5.2.2, Clause 1]** shall require the `request` or `request_uri` parameter to be passed as a JWS signed JWT as in clause 6 of OIDC;
- **[FAPI Part 2, Section 5.2.2, Clause 10]** shall require that all parameters are present inside the signed request object passed in the `request` or `request_uri` parameter;
- **[FAPI Part 2, Section 5.2.2, Clause13]** shall require the request object to contain an `exp` claim;
- **[FAPI Part 2, Section 8.6, Clause 1]** shall use `PS256` or `ES256` algorithms;

# PAR

**PAR** (**[OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/draft-ietf-oauth-par/)**) はもう一つの新しい仕様です。クライアントアプリケーションは認可リクエストを認可サーバーに事前登録し、登録した認可リクエストに対応するリクエスト URI の発行を受けます。そのリクエスト URI は、クライアントアプリケーションが認可リクエストを投げる際に `request_uri` パラメーターの値として用いることができます。

次の図は PAR の流れを示しています。詳細は『[図解 PAR : OAuth 2.0 Pushed Authorization Requests](https://qiita.com/TakahikoKawasaki/items/97563be46b62a0e02fe6)』を参照してください。

![par_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/d88cd106-4090-e1d4-3a23-8cf742d423fc.png)

私がここで PAR に言及した理由は、PAR 仕様が、PAR エンドポイント (Pushed Authorization Request Endpoint) に渡されるリクエストオブジェクトの処理を JAR 規則に従って処理するように要求しているからです。下記は PAR 仕様からの抜粋です。

> Clients MAY use the `request` parameter as defined in JAR to push a request object JWT to the authorization server. The rules for processing, signing, and encryption of the request object as defined in JAR apply.

PAR エンドポイントは登録された認可リクエストに対応するリクエスト URI を発行します。リクエスト URI の値と有効時間は、次の例のように、`request_uri` および `expires_in` の値として、PAR エンドポイントからのレスポンスに含まれます。

```json
{
  "request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
  "expires_in": 90
}
```

混乱を招きがちですが、発行されるリクエスト URI の有効時間と登録された認可リクエストの有効期限は異なります。次の表は、何がそれぞれの値に対応しているかを示しています。

|リクエスト URI| リクエスト URI の有効時間 | 参照される認可リクエストの有効期限 |
|:--|:--|:--|
| `request` を用いた PAR リクエストで発行 | PAR レスポンス内の `expires_in` | リクエストオブジェクト内の `exp` |
| `request` を用いない PAR リクエストで発行 | PAR レスポンス内の `expires_in` | N/A |
| その他 | N/A | 参照されるリクエストオブジェクトの内の `exp` |

見落としやすい点ですが、PAR エンドポイントの実装は、認可リクエストを保存する時にリクエストオブジェクトの `exp` クレームに関する情報を落としてはいけません。というのは、発行されたリクエスト URI が後で使われる際、`exp` クレームの値が参照されるかもしれないからです。例えば、[FAPI Part 2, Section 5.2.2, Clause 13] の検証ステップでは、`exp` クレームの存在が確認されます。

# メタデータ

JAR は、サーバーとクライアントのそれぞれに、`require_signed_request_object` というメタデータを定義しています。

Section 9.2 "OAuth Authorization Server Metadata Registry" にある、当該メタデータの説明は次のようになっています。

> Indicates where authorization request needs to be protected as Request Object and provided through either `request` or `request_uri` parameter

メタデータの名前と説明は、このメタデータは署名付きリクエストオブジェクトを要求するか否かを制御するだけかのような印象を与えます。しかし、Section 10.5 "Downgrade Attack" を読むと、それ以上の意味を持つことが分かります。

> When the value of it as a server metadata is `true`, then the server MUST reject the authorization request from any client that does not conform to this specification. It MUST also reject the request if the request object uses `"alg":"none"`. If omitted, the default value is `false`.

つまり、`"require_signed_request_object":true` は、(1) 認可リクエストは `request` または `request_uri` のいずれかを用いなければならない、**かつ**、(2) リクエストオブジェクトは JAR 規則に従って処理・検証される、ということを意味します。

`"require_signed_request_object":true` の際に期待される動作は、様々なユースケースをサポートしたい認可サーバーの実装にとっては、残念ながら柔軟とは言えません。そのため、ベンダーはカスタムオプションを用意することになるでしょう。例えば [Authlete](https://www.authlete.com/) (バージョン 2.2) は、上記の (1) と (2) に対応する二つの別々の設定項目を提供しています。[Connect2id](https://connect2id.com/) や [node-oidc-provider](https://github.com/panva/node-oidc-provider) などの他のベンダーや個人も、過去に OAuth メーリングリストで彼らの JAR に対するアプローチを説明しています。興味があれば[メールアーカイブ](https://mailarchive.ietf.org/arch/browse/oauth/)を覗いてみてください。

## 例 (Authlete)

![request-object-configuration-items_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/a31c283a-8ae2-915c-d2ef-bdab4f331112.png)

| リクエストオブジェクト |
|:--|
|「**必須**」を選択すると、このサービスへの認可リクエストは `request` または `request_uri` パラメーターを用いて常にリクエストオブジェクトを指定しなければなりません。<br><br>ここで**必須**を選択し、かつ、**リクエストオブジェクト処理**で **JAR 互換**を選択すると、サービスは `require_signed_request_object` サーバーメタデータが `true` であるかのように動作します。当該サーバーメタデータの詳細については JAR (JWT Secured Authorization Request) 仕様を参照してください。|

| リクエストオブジェクト処理 |
|:--|
|「**JAR 互換**」を選択すると、リクエストオブジェクトは JAR (JWT Secured Authorization Request) に定義されている規則に基づいて処理されます。「**後方互換**」を選択すると、OpenID Connect Core 1.0 に定義されている規則が適用されます。<br><br>JAR 規則と OIDC Core 1.0 規則の差分は次の通りです。<br><ul><li>JAR 規則では、リクエストオブジェクトの署名が常に要求されます。<li>JAR 規則では、リクエストオブジェクト外のリクエストパラメーターを参照することが禁止されます。結果として、JAR 規則が適用される場合、リクエストオブジェクト外のパラメーター群とリクエストオブジェクト内のパラメーター群はマージされなくなります。<li>OIDC Core 1.0 規則では、リクエストオブジェクト内に `response_type` が含まれる場合でも、`response_type` クエリー/フォームパラメーターをリクエストオブジェクト外にも置かなければなりません。一方、JAR 規則が適用される場合、リクエストオブジェクト外の `response_type` クエリー/フォームパラメーターは必須ではなくなります。しかし、代わりに、JAR 規則ではリクエストオブジェクトが常に `response_type` を含まなければなりません。<li>OIDC Core 1.0 規則では、OIDC リクエストは、リクエストオブジェクト内に `scope` が含まれる場合でも、`openid` を含む `scope` クエリー/フォームパラメーターをリクエストオブジェクト外にも置かなければなりません。一方、JAR 規則が適用される場合、リクエストオブジェクト外の `scope` クエリー/フォームパラメーターは必須ではなくなります。なお、認可リクエストが OIDC リクエストとして認識されるためには `openid` を含む `scope` をリクエストオブジェクト内に含めておく必要があります。</ul><br>ここで **JAR 互換**を選択し、かつ、**リクエストオブジェクト**で**必須**を選択すると、サービスは `require_signed_request_object` サーバーメタデータが `true` であるかのように動作します。当該サーバーメタデータの詳細については JAR 仕様を参照してください。|

# おわりに

FAPI バージョン 1 は 2020 年末までにファイナライズされ、FAPI バージョン 2 が始まります。JAR は、FAPI バージョン 2 の構成要素として採用されることになっています (参考：[FDX Global Summit Fall 2020](https://financialdataexchange.org/fdx/Events/Event_display.aspx?EventKey=DEVCON4_5) での崎村さんのキーノート "[Global Adoption of FAPI Among Open Banking Standards... And Beyond](https://openid.net/wordpress-content/uploads/2020/09/20200922-Global-Adoption-of-FAPI-EN_Nats-FDX-Keynote.pdf)")。

JAR は破壊的変更を含みますが、その採用は既定路線となっています。新しい仕様への準備をしていきましょう。