skip to content

What does signing an authorization request as an RFC 9101 request object prove that pushing it does not?

level: seniorimportance: should knowfreq 36%

answer

  1. trust in the object, not the channel
  2. signature travels with the parameters
  3. by value in request, by reference in request_uri
  4. client alg meets server supported list
  5. invalid_request_object is the rejection

basics

~20 s

A signed request object carries its own proof of authorship. The signature travels with the parameters, so an authorization server can verify who composed them even after the request has passed through a browser or been relayed by another party.

solid answer

~50 s

Pushing a request gives the authorization server confidence from the *channel*: the parameters arrived over an authenticated back-channel connection, and the server is trusting its own record of that call. RFC 9101 puts the confidence in the *object* instead. The client serialises the parameters as claims in a JWT — the request object — and sends it either by value in the `request` parameter or by reference through `request_uri`, with `client_id` still present as a plain parameter so the server can find the right key. The media type is `application/oauth-authz-req+jwt`, a rejected object yields `invalid_request_object`, and the algorithm is negotiated between the client's `request_object_signing_alg` and the server's `request_object_signing_alg_values_supported`. Because the proof rides inside the object, it survives the front channel, survives relaying, and is checkable after the fact — none of which a push provides.

code

json · 13 lines
json
{
  "iss": "s6BhdRkqt3",
  "aud": "https://as.example.net",
  "response_type": "code",
  "client_id": "s6BhdRkqt3",
  "redirect_uri": "https://display.example.org/cb",
  "scope": "meter.read",
  "state": "af0ifjsldkj",
  "code_challenge": "K2-ltc83acc4h0c9w6ESC_rEMTJ3bww-uCHaoeK1t8U",
  "code_challenge_method": "S256",
  "nbf": 1758290280,
  "exp": 1758290400
}

go deeper

for a junior

Recall the one-line difference: the request is packaged as a signed token, so the authorization server can check who wrote the parameters instead of trusting whatever arrived in the address bar.

for a middle

Be able to name the carriage forms and the plumbing: by value in request or by reference in request_uri, client_id still outside, the media type, the rejection error, and the two algorithm metadata fields that must agree.

for a senior

Demonstrate the distinction between channel trust and object trust, and state the limits out loud — a signature is not encryption, it does not shorten the URL, and it says nothing about the response leg.

for a principal

The call is where verifiable request origin is worth its operational weight: key distribution and rotation for every client, algorithm agreement across an estate, and the disputes you can actually settle with a retained signed object.

## Two different places to put the trust Both mechanisms answer "can the authorization server believe these parameters are the ones the client meant?", and they answer it in different places. - **Pushing** puts the trust in the **channel**. The parameters came in over a direct, server-authenticated connection from a client that authenticated itself, and the server is acting on its own stored copy. Nothing in the request is independently verifiable afterwards; the evidence is the server's log of the call. - **Signing** puts the trust in the **object**. The parameters are claims in a JWT signed by the client. Anyone holding the client's public key can check, at any later moment, that this exact parameter set was composed by that client and has not been altered by a character. That difference is why the two compose rather than compete: a pushed request that is also a signed request object gives a back-channel delivery **and** a verifiable artefact. ## How the object reaches the server RFC 9101 defines two carriage forms, and a request uses one of them: - **By value** — the whole JWT in the `request` parameter of the authorization request. - **By reference** — a `request_uri` the server resolves. With a pushed request that reference is the one the authorization server itself minted; RFC 9101 also allows a URI the server must dereference to obtain the object. A single authorization request does not carry both. In either form `client_id` is still sent as an ordinary parameter, because the server must identify the client before it can select a key and verify anything. ## What is inside it The request object's claims are the authorization request parameters — `response_type`, `client_id`, `redirect_uri`, `scope`, `state`, `code_challenge` — alongside JWT claims that scope the object itself: `iss` naming the client, `aud` naming the authorization server's `issuer` identifier so an object minted for one server cannot be replayed at another, and `exp`/`nbf` bounding when it may be used. Where a parameter appears both inside the object and duplicated in the query string, the specification's rule is that the object's values are the ones the server acts on; a duplicate outside is not merged in and cannot override. The practical consequence is that a query-string edit on a signed request either changes nothing or invalidates the signature. ## The negotiation and the failure code | Piece | Where it lives | What it does | |---|---|---| | `request_object_signing_alg` | Client metadata | Declares the algorithm this client will sign with | | `request_object_signing_alg_values_supported` | Authorization server metadata | Declares which algorithms the server will verify | | `require_signed_request_object` | Client metadata | Says this client only ever sends signed request objects | | `application/oauth-authz-req+jwt` | Media type | Identifies the object as an authorization request object | | `invalid_request_object` | Error code | Returned when the object is malformed, unverifiable or unacceptable | The two algorithm fields are a contract in two halves: a client that signs with something absent from the server's supported list is rejected before the request is ever shown to a resource owner. ## What signing buys, precisely 1. **Origin that survives the front channel.** The object can be handed to the browser and still be proved to have come from the client. 2. **Origin that survives relaying.** A party that merely forwards the object cannot change it, and does not need to be trusted for the server to accept the parameters. 3. **Evidence after the fact.** The signed object can be retained and re-verified — useful when a dispute is about what was actually asked for, not about what was granted. ## What signing does not buy - **It is not confidentiality.** A signed request object is readable by anything that handles it; a signature protects integrity and origin, nothing more. - **It does not shorten the URL.** By value, the JWT makes the authorization request considerably longer, which is one reason the two mechanisms are so often deployed together. - **It is not client authentication at the token endpoint.** The code exchange still authenticates the client on its own terms. - **It does not protect the response.** Everything here concerns the request leg; what comes back through the browser is a separate problem with separate machinery.

  • A parameter appears both inside the signed request object and in the query string with a different value. Which one does the server act on?
    The value inside the object. The duplicate outside is not merged and cannot override, which is what makes the signature meaningful. `client_id` is the exception that must appear outside as well, because the server needs it to select the key before it can verify anything.
  • What does `require_signed_request_object` change for a client?
    It records, in that client's metadata, that its authorization requests will always be protected as a request object delivered through `request` or `request_uri`. The authorization server can then reject an unsigned request from that client outright, rather than accepting whichever form happens to arrive.
  • If the parameters are already pushed over an authenticated back channel, is signing redundant?
    Not necessarily. Pushing gives the server confidence in the connection it received; signing gives a verifiable artefact that anyone with the key can check later, and that a third party can relay without being trusted. Where that evidence has no consumer, pushing alone is a reasonable place to stop.

saying these in an interview costs you the question

  • Thinks a signed request object also hides the parameters from the browser
  • Says the server merges query parameters that contradict the signed object
  • Sends request and request_uri together in one authorization request
  • Assumes any signing algorithm the client prefers will be accepted
  • Believes signing the request replaces client authentication at the token endpoint
  • Treats the signature as protecting the authorization response too