はじめに
この記事は、JAR と呼ばれる技術仕様『RFC 9101 The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR)』に関する実装者の覚書です。(英語版はこちら)
OpenID Connect Core 1.0 (OIDC Core) と JAR の間には幾つか衝突する部分があります。既存の認可サーバーだけでなく OpenID 互換性検査スイートにさえも影響を及ぼすため、OAuth コミュニティーはそれらの破壊的変更について長い間論争を続けました。
衝突する部分とは何か?それらはどのように解決されたのか? JAR 仕様の未来の読者や実装者のため、この記事はそれらについて記述します。
JAR とは?
JAR とは、認可リクエストパラメーター群を一つの署名付き JWT にまとめるための仕組みです。クライアントアプリケーションは、リクエストパラメーター群を個々に列挙する代わりに、その JWT を (ユーザーエージェントを介して) サーバーに送ります。
その JWT は『リクエストオブジェクト』と呼ばれます。リクエストオブジェクトは request
パラメーターの値としてサーバーに送られるか、もしくは、その場所が request_uri
パラメーターの値として送られます。
例えば、7 つの別々のクエリーパラメーターを持つ次の認可リクエストですが、
request
パラメーターを使うと次のようになります。
また、request_uri
パラメーターを使うと次のようになります。
7 つのリクエストパラメーターは全てリクエストオブジェクトの中に置かれており、また同時に、リクエストオブジェクトの外にも重複して client_id
パラメーターが置かれています。
JAR の初期ドラフトは、大胆にも client_id
を落として、認可リクエストは request
または request_uri
のいずれかのパラメーターのみを使うようにと要求していました。しかし、認可サーバー実装者の多くが client_id
は必須のまま残しておくべきだと主張しました。なぜでしょうか?理由は次の通りです。
- リクエストオブジェクトが対称鍵系アルゴリズムで暗号化されている場合、復号化のために共有秘密鍵が必要です。対称鍵系アルゴリズムでは、クライアントシークレットが鍵として使われるので (OIDC Core Section 10.1)、リクエストオブジェクトを復号化する前に、認可サーバーはクライアントアプリケーションを特定してデータベースからクライアントシークレットを取り出せなければなりません。暗号化されたリクエストオブジェクト内に
client_id
が含まれているとしても、認可サーバーは復号化の前にその値を知ることはできません。 - リクエストオブジェクトが
request_uri
で指定されている場合、認可サーバーはその場所にリクエストオブジェクトを取りにいかねばなりません。しかし、セキュリティー上の理由から、認可サーバーは事前登録されていない場所にリクエストオブジェクトを取りにいくことを拒否してもよい (個人的には「すべき」と思っています) とされています (参考:OpenID Connect Discovery 1.0, Section 3,require_request_uri_registration
)。提示されたリクエスト URI と当該クライアント用に事前登録されているリクエスト URI 群との照合をおこなうため、リクエストオブジェクトを取りにいく前に、認可サーバーはクライアントアプリケーションを特定できなければなりません。離れた場所にあるリクエストオブジェクト内にclient_id
が含まれているとしても、認可サーバーはリクエストオブジェクトを取りにいく前にその値を知ることはできません。
衝突箇所
リクエストオブジェクトは 2014 に世に出た OIDC Core の一部 (Section 6) として定義されました。JAR の目的は、リクエストオブジェクトの仕様を OIDC Core から取り出し、汎用的に使えるようにすることです。この作業にはそれほど時間がかからないだろうと関係者は考えていましたが、JAR により導入される破壊的変更について OAuth コミュニティーからの同意を得るまでに、最初のドラフトから実に 6 年以上も経過しました。
リクエストパラメーター群のマージ
リクエストパラメーター | OAuth 2.0 | OIDC | JAR |
---|---|---|---|
リクエストオブジェクト外の リクエストパラメーター群 |
マージされる | マージされる | 無視される |
リクエストオブジェクト内の リクエストパラメーター群 |
マージされる | マージされる | 参照される |
OIDC Core では、リクエストオブジェクト内外のリクエストパラメーター群はマージされます。これについては、Section 6.3.3 で次のように明確に述べられています。
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
orrequest_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) と OIDC Core では、response_type
は必須パラメーターとされています。リクエストオブジェクトが response_type
を含んでいたとしても、OIDC Core はリクエストオブジェクト外にも response_type
を要求します。OIDC Core の Section 6.1 に次のような記述があります。
So that the request is a valid OAuth 2.0 Authorization Request, values for the
response_type
andclient_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" (サーバーは重複を要求してはならない) でした。(FAPI Issue 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 から抜粋した scope
パラメーターに関する説明です。
REQUIRED. OpenID Connect requests MUST contain the
openid
scope value. If theopenid
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 は、リクエストオブジェクトが scope
を含み、その scope
が openid
を含む場合、リクエストオブジェクト外にも重複して scope
パラメーターを置くことを要求しています。
Even if a
scope
parameter is present in the Request Object value, ascope
parameter MUST always be passed using the OAuth 2.0 request syntax containing theopenid
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))。
結果として、JAR 規則が適用される場合、OIDC Core に準拠して「openid
を含む scope
をリクエストオブジェクト外に持たない OIDC リクエスト」を拒否する認可サーバーは、誤った実装とみなされることになります。
署名
署名 | OAuth 2.0 | OIDC | JAR |
---|---|---|---|
リクエストオブジェクトの署名 | 任意 | 任意 | 必須 |
JAR は、リクエストオブジェクトの署名を常に要求します。一方、OIDC Core では署名の無いリクエストオブジェクトも許容されます。OIDC Core の Section 6.1 は次のように述べています。
The Request Object MAY be signed or unsigned (plaintext)
興味深い副作用があります。もしも認可サーバーが常に JAR 規則を適用するのであれば、ディスカバリードキュメント (OpenID Connect Discovery 1.0) 内の request_object_signing_alg_values_supported
から none
を取り除くべきです。
FAPI
FAPI (Financial-grade API) は高度な API セキュリティーのための仕様です。FAPI は OIDC を基盤として策定されているので、FAPI の実装も JAR により影響を受けます。
参考までに、FAPI Part 2 には次に示すように、リクエストオブジェクトに関係する要求事項が幾つかあります。
-
[FAPI Part 2, Section 5.2.2, Clause 1] shall require the
request
orrequest_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
orrequest_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
orES256
algorithms;
PAR
PAR (OAuth 2.0 Pushed Authorization Requests) はもう一つの新しい仕様です。クライアントアプリケーションは認可リクエストを認可サーバーに事前登録し、登録した認可リクエストに対応するリクエスト URI の発行を受けます。そのリクエスト URI は、クライアントアプリケーションが認可リクエストを投げる際に request_uri
パラメーターの値として用いることができます。
次の図は PAR の流れを示しています。詳細は『図解 PAR : OAuth 2.0 Pushed Authorization Requests』を参照してください。
私がここで 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 エンドポイントからのレスポンスに含まれます。
{
"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
orrequest_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 isfalse
.
つまり、"require_signed_request_object":true
は、(1) 認可リクエストは request
または request_uri
のいずれかを用いなければならない、かつ、(2) リクエストオブジェクトは JAR 規則に従って処理・検証される、ということを意味します。
"require_signed_request_object":true
の際に期待される動作は、様々なユースケースをサポートしたい認可サーバーの実装にとっては、残念ながら柔軟とは言えません。そのため、ベンダーはカスタムオプションを用意することになるでしょう。例えば Authlete (バージョン 2.2) は、上記の (1) と (2) に対応する二つの別々の設定項目を提供しています。Connect2id や node-oidc-provider などの他のベンダーや個人も、過去に OAuth メーリングリストで彼らの JAR に対するアプローチを説明しています。興味があればメールアーカイブを覗いてみてください。
例 (Authlete)
リクエストオブジェクト |
---|
「必須」を選択すると、このサービスへの認可リクエストは request または request_uri パラメーターを用いて常にリクエストオブジェクトを指定しなければなりません。ここで必須を選択し、かつ、リクエストオブジェクト処理で JAR 互換を選択すると、サービスは require_signed_request_object サーバーメタデータが true であるかのように動作します。当該サーバーメタデータの詳細については JAR (JWT Secured Authorization Request) 仕様を参照してください。 |
リクエストオブジェクト処理 |
---|
「JAR 互換」を選択すると、リクエストオブジェクトは JAR (JWT Secured Authorization Request) に定義されている規則に基づいて処理されます。「後方互換」を選択すると、OpenID Connect Core 1.0 に定義されている規則が適用されます。 JAR 規則と OIDC Core 1.0 規則の差分は次の通りです。
ここで JAR 互換を選択し、かつ、リクエストオブジェクトで必須を選択すると、サービスは require_signed_request_object サーバーメタデータが true であるかのように動作します。当該サーバーメタデータの詳細については JAR 仕様を参照してください。 |
おわりに
FAPI バージョン 1 は 2020 年末までにファイナライズされ、FAPI バージョン 2 が始まります。JAR は、FAPI バージョン 2 の構成要素として採用されることになっています (参考:FDX Global Summit Fall 2020 での崎村さんのキーノート "Global Adoption of FAPI Among Open Banking Standards... And Beyond")。
JAR は破壊的変更を含みますが、その採用は既定路線となっています。新しい仕様への準備をしていきましょう。