OAUTH 2.0 · LAB
Follow the tenant's request object checks in order
Watch the tenant validate a request object step by step, see which step stops each failure the lesson lists, and close the unsigned route with require_signed_request_object.
PlannedUses your lab tenant
The lesson
Builds on: Building a request object.
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.
- G12 JAR
- G56 Client JWKS (`jwks` / `jwks_uri` per client)
Setup
Keep the key, claims and shell variables from Build a request object and send it by value or by reference.
Create a temporary second client in OAuth > Clients with the Web application preset: name
lab-tmp-vault, redirect URIhttp://127.0.0.1:8765/callback, scopesopenid photos.read. It stands in for Pixel Vault, another client with its own key. Generate that key now.
openssl ecparam -name prime256v1 -genkey -noout -out vault-1.pem
openssl ec -in vault-1.pem -pubout -out vault-1.pub.pem
Planned (G56): register
vault-1.pub.pemonlab-tmp-vaultwith key IDvault-1.Planned (G12): in OAuth > Flow policy, allow request objects by value, set a maximum lifetime of 60 minutes, require
typoauth-authz-req+jwt, and turnjtitracking on.
Note: the lab toolkit has no command yet that signs a JWT with your own key. The planned steps say exactly what to sign; they need that command before they can run.
Planned walkthrough
Send a valid signed object for
lab-printer, built as in the previous lab. Consent showsphotos.read.
Why it matters: a baseline that passes every check, so each failure below has one cause.
Send the same object with
&scope=openid%20photos.read%20photos.deleteadded to the URL. Consent still shows onlyphotos.read.
Why it matters: "Only the object counts". If any part of the server read the unsigned URL value, the signature would mean nothing.
Open OAuth > Clients > lab-printer > Request object test and paste the object. The tool lists the lesson's steps in order: client identified, object obtained, signature verified with
printer-2026-10from this client's registration,aud,expandnbfwithin policy,client_idmatches, parameters valid.
Why it matters: the report shows that the key came from the named client's registration, never from whichever registered key happens to verify.
In the same tool, run its built-in cases and note which step stops each one: a key not registered for this client,
algnone, anaudnaming another issuer, an expiredexp, a futurenbf, and an innerclient_idthat differs from the URL. Each stops at its own step withinvalid_request_object.
Why it matters: the protecting claims are only as strong as the policy the tenant enforces, and each has its own place in the order.
Sign a correct object for
lab-tmp-vaultwithvault-1.pemand send it withclient_idset to that client's ID. It succeeds. Present the same object withclient_id=$CLIENT_ID: refused.
Why it matters: each client's objects verify only with that client's registered keys, and the URL and inner client_id must agree.
Sign a correct object for
lab-printerthat asks forphotos.delete. The listener receiveserror=invalid_scope.
Why it matters: a valid signature proves who wrote the request. It does not make the request acceptable.
Turn on Require signed request objects for
lab-printerand open an ordinary unsigned authorization URL for it. The listener receiveserror=invalid_request.
Why it matters: "Errors and downgrades". Without this setting, signing is an option an attacker can simply decline.
Do today
Refusal, not partial processing. Send a
requestparameter together with ordinary parameters. The value is a stand-in; the tenant refuses on the parameter's presence.
curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&scope=$(urlenc 'openid photos.read')&state=jar-3&request=demo-request-object"
The redirect carries error=request_not_supported. The tenant refuses the whole request rather than quietly using the URL parameters, the behavior the lesson's downgrade warning depends on.
No trusted return address, no redirect. Send only
client_idandrequest, with noredirect_uri.
curl -s -o /dev/null -w '%{http_code}\n' "$ISSUER/oauth/authorize?client_id=$CLIENT_ID&request=demo-request-object"
The tenant shows its own error page (invalid_redirect_uri). It never reads a return address from an object it has not verified.
No fetch from an address the browser supplied. Send a reference that is an HTTPS address.
curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&state=jar-4&request_uri=$(urlenc https://printer.example/oauth/requests/demo-7)"
The redirect carries error=request_uri_not_supported, and the tenant made no outbound request. Audit shows oauth.authorize rejected request_uri_not_supported.
Write the order as your own checklist. Using the
CLAIMSyou built in the previous lab, walk the lesson's seven steps by hand: which client, which key list you would use, thenaudagainst$ISSUER,expandnbfagainstdate +%s, the innerclient_idagainst the URL, and only then the scope and return address.
Break it
These run once G12 exists.
Send one of your client assertions in
request. It is refused because itstypis notoauth-authz-req+jwt. The two kinds of JWT you sign stay separate.Send an object that fails validation and leave
redirect_uriout of the URL. With one registered return address the tenant may redirect there; with two or more it shows its error page. It never uses theredirect_uriinside an object it could not verify.
Check your work
Today, Audit shows oauth.authorize rejected request_not_supported and request_uri_not_supported for lab-printer. Once G12 exists, Audit also shows oauth.authorize rejections with a distinct reason for each failed step, and the refusal of the unsigned request after step 7.
Cleanup
Delete
lab-tmp-vaultin OAuth > Clients and removevault-1.pemandvault-1.pub.pem.Once G12 exists, turn Require signed request objects off on
lab-printer.
Missing infrastructure
G12: the ordered validation pipeline, a per-client and tenant-wide
require_signed_request_object, a request object test tool in the client screen, and a distinct Audit reason for each failed step (signature, unknown key, claims, client mismatch, type, signing required).G56: per-client public keys, so a key is chosen from the named client's registration only.
Once both exist and the toolkit can sign with the learner's own key, the Planned walkthrough runs as written.