OAuth and OIDC request objects: how to pass them, and how to validate, sign, encrypt and use them in authorization requests.

OAuth and OIDC Request Objects

On this page

Both OpenID Connect and OAuth 2.0 (through JAR) 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 or Authorization Server.

Although the JWT Authentication Request specification (JAR) for OAuth 2.0 is based on the Request Object defined in OpenID Connect core, there are some significant changes between these specifications - even if they seem minor at first. For example, there is a difference in which request parameters should be used in the query string or how to behave if parameters are sent both in the query string and the Request Object. What is more, the Part 2 of the Financial-Grade API Security Profile (FAPI) adds additional requirements for the JWT used as the Request Object. All this can create some confusion for implementers as, unfortunately, some requirements are contradictory. It means that most often, companies implementing Clients (Relying Parties), will have to decide which standard they need to adhere to, based on the security requirements of their project. For companies which already use the OIDC version of Request Objects, it may be a problem to move to the JAR specification, as it may break their existing integrations.

The Problem With Authorization Request

A URL-encoded query string includes a set of parameters required by the Authorization Request:

http
123456789
GET /oauth/v2/oauth-authorize HTTP/1.1
Host: idsvr.example.com
Content-Type: application/x-www-form-urlencoded
 
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 leaked or even be modified by an attacker. Even though the request is sent via a secure TLS connection, this only protects it from being eavesdropped in transport. TLS is terminated in the browser, so the request is no longer protected in communication between the browser and the Client. At the server side, the TLS session might be terminated by a load-balancer, which again means that the request will not be protected by TLS at some point. This leads to a few problems. Firstly, it means that the request can be viewed at some parts of the transport. It might, for example end up in logs, which might not be desirable, if some parameters should remain confidential. Secondly, the Authorization Server has no means of verifying the integrity of the Authorization Request. If the Client was a victim of a man-in-the-browser attack, the request parameters could have been tampered with. Lastly, the Authorization Server has no means of verifying the authenticity of the origin of the Authorization Request - even though a client ID is used in the request, the Authorization Server can't be sure that it was indeed that Client who initiated the request.

Certain environments require additional security, so they need the aforementioned problems to be addressed. 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 some attacks on the Authorization Request.

This approach is especially useful in APIs that handle sensitive data, like financial-grade APIs. In fact, Request Objects are required by the Financial-grade API Security Profile (FAPI) for APIs which handle both read and write capabilities (Part 2).

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.

json
1234567
{
"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.

Depending on the specification used, some parameters still have to be present in the query string. If you're using the OIDC spec, it is mandatory to pass client_id and response_type on the query string. It is also mandatory to pass the openid scope (scope=openid) to indicate to the Provider that an OpenID request is being sent.

JAR specification only requires client_id on the query string.

In the OIDC specification, 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. According to JAR, request parameters on the query string other than client_id (and the Request Object itself) should be ignored. The client_id parameter must still match the client ID used in the 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.

http
12345678
GET /oauth/v2/oauth-authorize? HTTP/1.1
Host: idsvr.example.com
Content-Type: application/x-www-form-urlencoded
 
client_id=client-one&
response_type=code&
scope=openid&
request=eyJhbGciOiJSUzUxMiJ9.eyJyZXNwb25zZV90eXBlIjoi...

By Reference

The by reference option is useful when the Request Object would be very large due to many claims. Such a scenario may exceed the URL size limit for a browser. The by reference option could also be used to further increase security of the request. The reference to the Authorization Request can be handled by a third-party service whom both the Client and the Authorization Server trust. In such a scenario, the Authorization Server knows that the third-party service must have verified the Client in order to create a reference to the Request Object. This adds another layer of trust and security to the Authorization Request.

The Request Object is 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.

http
12345678
GET /oauth/v2/oauth-authorize HTTP/1.1
Host: idsvr.example.com
Content-Type: application/x-www-form-urlencoded
 
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 in the OIDC specification, but can be used to protect against tampering by digitally signing, or can fully protect the contents of the Request Object through encryption. FAPI profile requires the Request Objects to be signed (it also allows only a subset of signing algorithms, e.g. symmetric signing is not permitted).

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.

The Client can inform the Authorization Server which algorithm it will be using for signing through the request_object_signing_alg metadata parameter. If provided, then the Authorization Server should reject any Request Objects from the client signed with a different algorithm.

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.

The JAR specification adds another metadata entry - require_signed_request_object, which, if set to true on the server side, requires clients to use signed Request Objects for Authorization Requests. If this metadata is set on the Client, it tells the Authorization Server, that it should reject Authorization Requests for this Client which do not use signed Request Objects.

The Curity Identity Server does support these methods.

When To Use

An important thing to remember is to know the costs of the features you decide to use. You should implement Request Objects when you know you will be getting real additional value out of this feature, e.g. if your project deals with highly sensitive data or operations.

Using Request Objects for Authorization Requests, on one hand, gives you better security and guards against some attack vectors on the OAuth and OpenID Connect protocols, but, as with many security features, it also adds complexity to your project. If you want your Client to issue signed Request Objects, you need the Client to be able to forge and sign JWTs using a private key. This key must be kept secure, so that no one else is able to sign the Request Object. The corresponding public key must be registered at the Authorization Server, which complicates key management. If you want to use Request Objects by reference, you either need your Client to expose secure endpoint with the Request Object value, or use a third-party service, which may require you to use more resources (there will be more traffic between services, maybe the third-party service must be paid for, etc.).

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 might be dangerous. This request passes the information in a way that could potentially be eavesdropped, or even 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.

Join our Newsletter

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

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial