Building a request object
To sign its authorization requests, the printer needs a key pair whose public key the photo service holds. Secrets and signed assertions described that arrangement for client authentication, and the printer now adopts it. It creates the key printer-2026-10, keeps the private key on its backend, publishes the public key in its key set at https://printer.example/oauth/jwks.json, and registers that address with the photo service.
At the same time, it switches its registration from client_secret_basic to private_key_jwt. Once the printer manages a key pair and a published key set, keeping a shared secret beside them would mean one more credential to protect and rotate, and the weaker of the two. Deployments that sign their requests for assurance typically authenticate with keys as well, and the high-assurance profiles mentioned in that lesson require it. From here on, the printer authenticates with client assertions signed by the same key.
Building a request object then takes three decisions: which claims go into it, how it reaches the photo service, and whether to encrypt it.
From parameters to claims
All domains, keys, and identifiers in these examples are fictional. Here is the printer's familiar authorization request as a request object, decoded so that its header and claims are readable:
Header:
{
"alg": "ES256",
"kid": "printer-2026-10",
"typ": "oauth-authz-req+jwt"
}
Claims:
{
"iss": "photo-printer",
"aud": "https://auth.photos.example",
"response_type": "code",
"client_id": "photo-printer",
"redirect_uri": "https://printer.example/oauth/callback",
"scope": "photos.read",
"state": "demo-attempt-7",
"code_challenge": "qd-75t-gnweZAhIl6REjxxgFnPXGwvqYcxq3vlsaJYQ",
"code_challenge_method": "S256",
"iat": 1790845200,
"nbf": 1790845200,
"exp": 1790845500,
"jti": "demo-request-object-7"
}
The middle of the claims is the authorization request you know, from response_type to code_challenge_method. Each parameter becomes a claim with the same name. Values are ordinary JSON: strings stay strings and numbers become JSON numbers. The return address is no longer URL-encoded, because it is no longer inside a URL.
The object must contain every parameter the server needs to process the request, including any extensions the printer uses. Two parameters are never allowed inside it: request and request_uri, the parameters that carry a request object. An object cannot point to another object.
Claims that protect the object
The remaining claims are not authorization parameters. They describe the object itself, so that the signature covers who made it, for whom, and for how long.
iss is the printer's client ID, naming who issued the object, and aud is the photo service's issuer identifier, naming who should accept it. The specification says a signed request object should contain both. An object addressed to the photo service is useless at Pixel Vault, the other photo service the printer supports, even if the printer signs its requests for both with the same key.
iat, nbf, and exp record when the object was issued, when it becomes valid, and when it expires, as Unix timestamps. These values correspond to 2026-10-01 09:00 UTC for the first two and 09:05 UTC for the last, so the object works for five minutes. jti gives it a unique identifier, which lets the server refuse it if it is presented twice. RFC 9101 requires none of these four claims. It reserves their names, so that no authorization request parameter can ever give them another meaning, and leaves the rest to servers. Many servers require them, and high-assurance profiles require nbf and exp with a limited lifetime.
The header's typ value, oauth-authz-req+jwt, is the media type registered for request objects, written without its application/ prefix. Like the type on the signed lab result in Digital signatures, it declares what kind of JWT this is, and that matters here because the printer now signs two kinds of JWT with one key. A server that requires this type at the authorization endpoint refuses anything else offered as a request object, including one of the printer's client assertions.
The opposite direction depends on the claims. A request object's iss and aud look exactly like a client assertion's, and a server checking a client assertion cannot rely on a type to tell them apart: client assertions were defined without one, and newer guidance that recommends client-authentication+jwt also advises servers not to reject assertions that lack it. What client assertions have always required is that sub name the client. RFC 9101 therefore advises never using the client ID as a request object's sub, and the printer's object has no sub at all, so it fails if anyone presents it as a client assertion. The specification also mentions a third safeguard, a separate key for each purpose, which some deployments adopt.
Sending it by value or by reference
The printer signs the object and can now send it in one of three ways. The simplest puts it directly in the browser URL, in the request parameter. This is sending it by value:
https://auth.photos.example/authorize
?client_id=photo-printer
&request=eyJhbGciOiJFUzI1NiIsImtpZCI6InByaW50ZXItMjAyNi0xMCIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QifQ.eyJpc3MiOiJwaG90by1wcmludGVyIiwiYXVkIjoiaHR0cHM6Ly9hdXRoLnBo...
The object is shortened here for display. client_id also appears outside it, and the two values must be identical. Signing makes the request longer: the ordinary URL for this request is about 270 characters, and this one is nearly 770. A request with more detail grows accordingly.
Sending it by reference keeps the URL short. The printer publishes the object at an HTTPS address of its own and passes that address in request_uri:
https://auth.photos.example/authorize
?client_id=photo-printer
&request_uri=https%3A%2F%2Fprinter.example%2Foauth%2Frequests%2Fdemo-request-object-7
The photo service then fetches it:
GET /oauth/requests/demo-request-object-7 HTTP/1.1
Host: printer.example
HTTP/1.1 200 OK
Content-Type: application/oauth-authz-req+jwt
eyJhbGciOiJFUzI1NiIsImtpZCI6InByaW50ZXItMjAyNi0xMCIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QifQ...
The address must use HTTPS and be reachable by the photo service, and the whole value should not exceed 512 characters. Because anyone who learns the address could fetch the object, a real address contains a long random part and the object is removed after use or a short time. Many servers accept references only on addresses the client registered in advance.
The third way is to push the object to a PAR endpoint, where the server issues the reference itself. Using JAR with PAR follows that route.
Encrypting the request object
Signing protects the request from changes, but anyone who sees the object can decode and read it. Sent by value, it passes through the browser and every place a URL is recorded. If a request carries something sensitive, such as personal details or the particulars of a payment, the specification says the client should also encrypt it.
The order is fixed: sign first, then encrypt. The printer encrypts the signed object with the photo service's public encryption key, using JSON Web Encryption (JWE), the encrypted counterpart of the signed format that JWTs usually take. The result is a nested JWT, a signed JWT inside an encrypted one. Its compact form has five dot-separated parts instead of three, and only the photo service, holding the private key, can open it.
Encryption is not a substitute for the signature. Anyone can encrypt a message to a public key, so an encrypted object on its own says nothing about who created it. The server decrypts, then verifies the signature inside.
Both sides have to agree on algorithms. The photo service lists those it accepts in its metadata, under names such as request_object_signing_alg_values_supported and request_object_encryption_alg_values_supported, and a client can record its own choice in its registration, for example request_object_signing_alg set to ES256. With that settled, the printer's object is complete: a signed statement of exactly what it asks for, addressed to one authorization server and valid for five minutes.