Signed Request Object

Signed Request Object

architect

Both OpenID Connect and OAuth 2.0 define ways to send query parameters encoded in a JWT instead of passing them as parameters in the URL. The JWT, in this case, is referred to as a Request Object. The purpose of using a Signed Request Object is to further secure the authorization request sent to the OpenID Provider.

A URL encoded query string includes a set of required parameters:

https://idsvr.example.com/oauth/v2/oauth-authorize?
&client_id=client-one
&state=1601047943181-UOk
&scope=openid%20address%20secret_scope
&response_type=code
&redirect_uri=https://oauth.tools/callback/code

The information in the query string could potentially be viewable between the client-side application where the request is originating and the authorization server. Not only could it be viewable, but it could also be a target for tampering through a man-in-the-browser attack.

Certain environments require additional security, and that’s where the Request Object approach comes in. Instead of passing all parameters in the URL query string, it is possible to move some or all of them to a JWT. The JWT can then optionally be signed and/or encrypted to further strengthen the validity and security. Doing so can help safeguard against attacks.

An example where this is required is a Financial-grade API (FAPI) that must handle both read and write (Part 2) capabilities.

Request Object

The request parameters are added as claims in the Request Object. Based on the earlier example of the URL encoded query string, the Request Object would look something like this.

{
  "aud": "https://idsvr.example.com",
  "iss": "client-one",
  "response_type": "code",
  "redirect_uri": "https://oauth.tools/callback/code",
  "scope": "openid address secret_scope"
}

Note the two additional claims that are needed in the Request Object that are not part of the URL query string: iss and aud . iss is the ID of the Relying Party (i.e. client_id) and aud is the value the OpenID Provider uses as the issuer or its token endpoint.

It is still mandatory for an OpenID flow to pass client_id and response_type on the query string since the OpenID specification requires them. It is also mandatory to pass the openid scope (scope=openid) to indicate to the Provider that an OpenID request is being sent.

The OAuth specification is a bit more relaxed here — it only requires client_id on the query string.

If a claim exists in the query string and the Request Object, the Request Object’s value will supersede the query string’s value. The exception to this is the client_id and response_type; these values have to match in the query string and Request Object.

There are two different ways of passing a Request Object: by value or by reference.

By Value

Using the by value method, the request parameter is added to the query string and its value is the Request Object itself.

https://idsvr.example.com/oauth/v2/oauth-authorize?
&client_id=client-one
&response_type=code
&scope=openid
&request=eyJhbGciOiJSUzUxMiJ9.eyJyZXNwb25zZV90eXBlIjoi...

By Reference

The by reference option is useful when the Request Object could be very large due to many claims. Such a scenario may exceed the URL size limit for a browser.

The Request Object is then passed as a reference parameter in the query string using the request_uri parameter. The value references the Request Object, which the Provider can use to download the Request Object itself.

https://idsvr.example.com/oauth/v2/oauth-authorize?
&client_id=client-one
&response_type=code
&scope=openid
&request_uri=https://example.com/request.jwt#MjZhNTY2NTVlNDgzNDE5ODAxMjZkYzRhMWM2NmFiYjU1ZWVkMzBjOTM5MzJiM2VjYjliY2ZkNGIyMmJiZjAwZA==

Within an OpenID flow, the request_uri parameter also contains a base64-encoded SHA-256 hash of the entire content of the Request Object included in the URL (Shown above as #MjZhNTY2...). This allows the Provider to cache the token for future use. Caching can optimize performance by avoiding a download when the Request Object has not changed.

One aspect to consider with the by reference approach is that the Service Level Agreement (SLA) of the Provider essentially becomes the SLA of the client. If the client cannot provide the same level of service, using a URL is not a good idea.

Instead, use Pushed Authorization Requests (PAR). Using PAR, the client can send the Request Object in a POST and receive a URN that references the POSTed Request Object. The reference URN can then be used as the value of the request_uri claim.

Validation, Signing, and Encryption

The first step for the Provider receiving a Request Object is to validate that it is actually a valid Request Object. This is in addition to the standard validation of a valid Authorization request.

Signing and encryption are not mandatory but can be used to protect against tampering, digitally sign, or fully protect the Request Object.

To denote that signing the Request Object is required, the Provider can expose request_object_signing_alg_values_supported on the metadata endpoint. The algorithm(s) supported for signing by the client are exposed in the metadata. Signing the Request Object would eliminate attacks that tamper with the information in the Request Object. Regardless of what method is used to pass the Request Object, once signed, the Provider has to verify the signature.

Encryption is noted by the Provider exposing request_object_encryption_alg_values_supported and request_object_encryption_enc_values_supported in the OpenID Discovery Document or in some other way. This information can then be used to fully encrypt the Request Object, and with that, fully protect the information from any outside visibility. When enabled, the Authorization Server must decrypt the Request Object, and if the content is signed, must validate the signature.

Support

Not all Providers will support the Request Object approach automatically. You must first enable it on the Provider side to expose the support in its metadata.

request_parameter_supported: true

request_uri_parameter_supported: true

These parameters above would indicate that the Provider supports the different Request Object request methods. Parameters outlined in the previous section would indicate if the Provider would require either signing, encryption, or both.

Note that the OAuth specification adds a boolean request_object_signing_alg to the metadata. If true, then the server requires the request parameters to be sent through a signed Request Object.

The Curity Identity Server does support these methods.

Summary

A Request Object, especially a signed and encrypted Request Object, can help safeguard against Authorization Request attacks.

A traditional Authorization Request sent as a query string URL is very dangerous. This request passes the information in a way that could potentially be eavesdropped, or worse, be manipulated.

Passing the parameters as claims in a signed JWT improves security but only goes so far — it safeguards against data manipulation but doesn’t prevent eavesdropping. Encrypting the signed JWT is the only way to fully protect the information from access by outside parties. This high level of security is typically a requirement within token-based architectures such as financial-grade APIs.

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

Keep up with our latest articles and how-tos using RSS feeds