Implementer’s note about JAR (JWT-Secured Authorization Request)
Introduction
This article is an implementer’s note about a technical specification called “JAR”, RFC 9101 The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR).
There exist some conflicts between OpenID Connect Core 1.0 (OIDC Core) and JAR. The OAuth community had a long dispute about the breaking changes because they affect existing authorization server implementations and even the OpenID conformance suite.
What are the conflicts? How were they solved? This article describes them for future readers and implementers of the JAR specification.
What is JAR?
JAR is a mechanism whereby to pack authorization request parameters into a signed JWT. A client application sends the JWT to the server (via a user agent) instead of listing the request parameters separately.
The JWT is called “request object”. It is sent to the server as the value of the request
parameter, or its location is sent as the value of the request_uri
parameter.
For example, the following authorization request which has 7 separate query parameters:
becomes like below with the request
parameter,
or like below with the request_uri
parameter.
All the 7 request parameters are put in the request object, and at the same time, the client_id
parameter is present outside the request object duplicately.
An early draft of JAR was so bold to drop client_id
and require that either request
or request_uri
be used as the only parameter of an authorization request. However, most of active authorization server implementers insisted that client_id
should remain mandatory. Why? The reasons are as follows.
- If a request object is encrypted with a symmetric algorithm, the shared secret key is needed to decrypt it. Because the key for symmetric algorithms is the client secret (OIDC Core Section 10.1), the authorization server must be able to identify the client application to extract the client secret from its database before decrypting the request object. Even if the encrypted request object includes
client_id
inside it, the authorization server cannot know the value before decryption. - If a request object is specified by
request_uri
, the authorization server must fetch the request object from the location. However, for security reasons, the authorization server may (I think it “should”) refuse to fetch a request object from an unregistered location (cf. OpenID Connect Discovery 1.0, Section 3,require_request_uri_registration
). To collate the presented request URI with pre-registered ones of the client application, the authorization server must be able to identify the client application before fetching the request object. Even if the remote request object includesclient_id
inside it, the authorization server cannot know the value before fetching.
Conflicts
Request object was defined as a part of OIDC Core (Section 6) in 2014. JAR’s purpose is to extract the specification of request object from OIDC Core and make it usable for generic purposes. Those involved thought the task wouldn’t take much time, but over 6 years have passed since the first draft before breaking changes introduced by JAR got consent from the OAuth community.
Request Parameter Assembly
In OIDC Core, request parameters inside a request object and ones outside the request object are merged as Section 6.3.3 explictly states as follows.
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.
On the other hand, JAR prohibits referring to request parameters outside a request object by stating as follows in Section 5,
However, the authorization server supporting this specification MUST only use the parameters included in the request object.
and in 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
In OAuth 2.0 (RFC 6749) and OIDC Core, response_type
is a mandatory parameter. Even if a request object includes response_type
, OIDC Core requires response_type
outside the request object. Section 6.1 of OIDC Core explicitly states as follows.
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.
Apparently, OIDC Core has taken compatibility with OAuth 2.0 seriously. However, JAR has decided not to require response_type
outside a request object in spite of some objections from the community.
As a result, when JAR rules apply, authorization servers that conform to OIDC Core and reject OIDC requests that don’t come with response_type
outside a request object are regarded as wrong implementations.
Of course, some people suggested that the OIDC Core’s requirement on response_type
should remain effective even when JAR rules apply. However, the reached consensus is “servers must not require the duplicates.” (FAPI Issue 315)
Changing “mandatory” to “optional|ignored” has a bigger impact on implementations than the opposite direction because developers must review and modify source programs that are written on the assumption that mandatory parameters are always available once sanity parameter checks in an early phase are done.
scope
The scope
parameter is optional in OAuth 2.0, but OIDC Core has made it mandatory. The following is the description about the scope
parameter excerpted from Section 3.1.2.1 of OIDC Core.
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.
In addition, Section 6.1 requires that scope
be present outside a request object duplicately when the request object includes scope
and the scope
includes openid
.
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.
If the same conclusion as response_type
applies, servers must not require that scope
be present duplicately. The relevant discussion could be found in the archive of the OAuth mailing list ([OAUTH-WG] [JAR] scope parameter outside request object of OIDC request).
As a result, when JAR rules apply, authorization servers that conform to OIDC Core and reject OIDC requests that don’t come with scope
including openid
outside a request object are regarded as wrong implementations.
Signing
JAR requires that a request object be always signed. On the other hand, OIDC Core allows unsigned request objects. Section 6.1 of OIDC Core states as folows.
The Request Object MAY be signed or unsigned (plaintext)
An interesting side effect is that none
should be removed from request_object_signing_alg_values_supported
in the discovery document (OpenID Connect Discovery 1.0) if the authorization server always forces JAR rules.
FAPI
“FAPI”, Financial-grade API, is a specification for enhanced API security. Because FAPI is built on top of OIDC, FAPI implementations are affected by JAR.
FYI: FAPI Part 2 has some requirements related to request objects as follows.
- [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, is another new specification. It enables client applications to pre-register authorization requests into the authorization server and get request URIs that correspond to the registered authorization requests. The request URIs can be used as the value of the request_uri
parameter when the client applications make an authorization request later.
The diagram below illustrates the flow of PAR. See “Illustrated PAR: OAuth 2.0 Pushed Authorization Requests” for details.
The reason I mentioned PAR here is that PAR requires that a request object given to the pushed authorization request endpoint be processed based on JAR rules. The following is an excerpt from the PAR specification.
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.
The PAR endpoint issues a request URI that represents the registered authorization request. The value and lifetime of the request URI are contained in the response from the PAR endpoint as the values of request_uri
and expires_in
like below.
{
"request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
"expires_in": 90
}
A confusing point here is that the lifetime of the issued request URI and the expiration time of the registered authorization request are different. The table below shows what correspond to the lifetime and the expiration time.
An easily-overlooked point is that a PAR endpoint implementation must not drop information about theexp
claim of a given request object when it saves the authorization request because the exp
claim may be referenced later when the issued request URI is used. For example, the validation step for [FAPI Part 2, Section 5.2.2, Clause 13] checks the existence of the exp
claim.
Metadata
JAR defines require_signed_request_object
metadata for both server and client, respectively.
The description of the boolean metadata in Section 9.2 “OAuth Authorization Server Metadata Registry” is simple as excerpted below.
Indicates where authorization request needs to be protected as Request Object and provided through either
request
orrequest_uri
parameter
The name and description of the metadata give an impression that the metadata just controls whether a signed request object is required or not. However, you will notice it means much more when you read a paragraph in 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
.
That is, "require_signed_request_object":true
means (1) that an authorization request must use either request
or request_uri
AND (2) that the request object is processed and validated based on JAR rules.
The behavior expected in the case of "require_signed_request_object":true
is unfortunately inflexible for authorization server implementations that want to support various use cases. Therefore, vendors will provide custom options. For example, Authlete (version 2.2) provides two separate configuration items that correspond to (1) and (2) above. Some other vendors and individuals such as Connect2id and node-oidc-provider also explained their approaches for JAR in the OAuth mailing list in the past. Please dig the mail archive if you are interested.
Example (Authlete)
[Request Object]
If Mandatory is selected, authorization requests to this service must always specify a request object by using request
or request_uri
parameter.
If Mandatory is selected here and JAR compatible is selected in Request Object Processing, this service behaves as if require_signed_request_object
server metadata is true
. See the specification of JAR (JWT Secured Authorization Request) for details about the server metadata.
[Request Object Processing]
If JAR compatible is selected, request objects are processed based on the rules defined in JAR (JWT Secured Authorization Request). If Backward compatible is selected, the rules defined in OpenID Connect Core 1.0 apply.
Differences between JAR rules and OIDC Core 1.0 rules are as follows.
- JAR requires that request objects be always signed.
- JAR prohibits referring to request parameters outside the request object. As a result, when JAR rules apply, parameters outside the request object and parameters inside the request object are not merged.
- OIDC Core 1.0 requires that
response_type
query/form parameter be present outside the request object even when the request object containsresponse_type
. On the contrary, when JAR rules apply,response_type
query/form parameter outside the request object is no longer mandatory. However, instead, it means JAR requires thatresponse_type
be always included in the request object. - OIDC Core 1.0 requires that an OIDC request come with
scope
query/form parameter includingopenid
even when the request object containsscope
. On the contrary, when JAR rules apply,scope
query/parameter parameter outside the request object is no longer mandatory. Note that the request object must containscope
includingopenid
so that the authorization request can be regarded as an OIDC request.
If JAR compatible is selected here and Mandatory is selected in Request Object, this service behaves as if require_signed_request_object
server metadata is true
. See the specification of JAR for details about the server metadata.
Finally
FAPI version 1 will be finalized by the end of 2020 and FAPI version 2 will start. JAR is to be adopted as a component of FAPI version 2 (cf. “Global Adoption of FAPI Among Open Banking Standards… And Beyond”, Nat’s keynote at FDX Global Summit Fall 2020).
JAR contains some breaking changes, but its adoption is an established policy. Let’s be prepared for the new specification.