Validating a request object
Your browser arrives at the photo service carrying client_id=photo-printer and a request object. Decoding the object would show a scope, a return address, and everything else the printer asked for. None of it means anything yet. Until the photo service has established that the object is genuine, current, and meant for it, those claims are only claims.
Before any parameter is used
The photo service works through the object in an order that keeps it from relying on anything it has not checked:
- Identify the client. Look up the registration for the
client_idin the URL. Exactly one ofrequestandrequest_urimay be present. - Obtain the object. Take it from
request, or fetch it fromrequest_uriunder the precautions described below. - Decrypt it if it is encrypted, using the photo service's own private key. If decryption fails, stop.
- Verify the signature. The key must belong to this client. If the header names a key with
kid, that must be the key used, and it must be one registered forphoto-printer, either directly or in the key set the printer registered. The algorithm must be one the service allows for this client. An object whosealgisnonecarries no signature and proves nothing, and a service that requires signed objects must refuse it. - Check the claims that protect the object. Under the ordinary JWT rules, an
audthat is present must name the photo service's issuer identifier, anexpmust not have passed, and annbfmust have arrived. RFC 9101 asks only that a signed object includeissandaud, so the rest is the service's policy, or the policy of a profile it follows: which claims must be present, whetherissmust equal the client ID, the longest lifetime it accepts, whethertypmust beoauth-authz-req+jwt, and whether it tracksjtivalues to refuse repeats. - Match the client. The
client_idclaim inside the object must be identical to theclient_idin the URL. - Validate the request. Only now read the authorization parameters and check them as for any authorization request: the return address against the registration, the scope against what the printer may request, the PKCE challenge, and the rest.
The last step is easy to forget once a signature has verified. The signature proves that the printer wrote the request. It does not make the request acceptable. A correctly signed object asking for photos.delete is refused for the same reason an unsigned one would be: the printer's registration does not allow that scope.
The key check in step 4 deserves attention too. The service holds public keys for many clients. Verifying the printer's request with whichever key happens to make the signature valid would let any client sign requests that name the printer. The key has to come from the printer's own registration.
Only the object counts
All domains and values in these examples are fictional. A client may repeat parameters outside the object, for example to work with older servers, and someone may add parameters in the browser:
https://auth.photos.example/authorize
?client_id=photo-printer
&request=eyJhbGciOiJFUzI1NiIsImtpZCI6InByaW50ZXItMjAyNi0xMCIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QifQ...
&scope=photos.read%20photos.delete
The object is shortened here for display. The photo service uses only the parameters inside the object. The scope in the URL is ignored, even though it names a different value. That rule is what makes the signature meaningful. If one part of the service read the scope from the URL while another verified the signature over the object, an attacker could pair a genuine signed object with whatever unsigned values they liked.
client_id is the one value that must appear in both places, and step 6 requires the two to be identical for the same reason: a request should never be able to name one client in the URL while carrying another client's object.
OpenID Connect, which used request objects before JAR, follows a different rule. It assembles the request from both sources and lets the object's value win where they overlap. A server that supports both needs to be clear about which rule applies, and a client should not rely on values outside the object.
Fetching a request by reference
When the printer passes its object by reference, step 2 means the photo service sends a request of its own to an address it received from a browser. The request_uri value is not signed, because it travels in the URL, so malicious software in the browser could change it. That makes the fetch an opportunity for abuse. An attacker could point the service at an enormous or deliberately slow resource to tie it up, at another site the attacker wants to flood with requests, or at an address inside the service's own network. A reference could even point to another authorization request that carries its own reference, sending the service round in circles.
The service protects itself in a few ways. It fetches only from locations it expects for this client, such as addresses registered in advance. It checks that the response has the media type application/oauth-authz-req+jwt. It limits how long it waits and how much it will read. And it never follows a reference found inside what it fetched, which the rule against request_uri inside an object already forbids.
A fetched object is checked exactly like one sent by value. Retrieving it from printer.example over HTTPS does not replace the signature check, because it is the signature, not the address it came from, that shows the printer created it.
Errors and downgrades
JAR adds four error codes to those an authorization endpoint can return:
| Error | Meaning |
|---|---|
invalid_request_object | The object is invalid, for example because decryption or signature verification failed or the key does not belong to the client. |
invalid_request_uri | Fetching the request_uri failed or returned invalid data. |
request_not_supported | The server does not accept the request parameter. |
request_uri_not_supported | The server does not accept the request_uri parameter. |
These errors go back to the client as authorization error responses, but only to a return address the service can trust. A redirect_uri read from an object whose signature failed is not trustworthy. The service either uses an address it can establish from the registration alone or shows an error page, as for any request with a doubtful return address.
The most damaging failure is quieter. If the photo service verifies signed objects but also still accepts ordinary unsigned requests from the printer, an attacker can skip the signature entirely by sending an ordinary request. To close that route, a client registration or the server as a whole can set require_signed_request_object to true:
{
"client_id": "photo-printer",
"token_endpoint_auth_method": "private_key_jwt",
"jwks_uri": "https://printer.example/oauth/jwks.json",
"request_object_signing_alg": "ES256",
"require_signed_request_object": true
}
The three entries after the client ID record the arrangement from Building a request object: client assertions, the key set that holds printer-2026-10, and the algorithm for request objects. The last entry closes the old route. With it, the photo service must reject any authorization request from the printer that does not carry a request object, and any object that uses alg none. As server metadata, the same setting applies to every client. Without it, signing is only an option the printer has chosen, and an option an attacker can decline.