Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

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.

Setup

  1. Keep the key, claims and shell variables from Build a request object and send it by value or by reference.

  2. Create a temporary second client in OAuth > Clients with the Web application preset: name lab-tmp-vault, redirect URI http://127.0.0.1:8765/callback, scopes openid 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
  1. Planned (G56): register vault-1.pub.pem on lab-tmp-vault with key ID vault-1.

  2. Planned (G12): in OAuth > Flow policy, allow request objects by value, set a maximum lifetime of 60 minutes, require typ oauth-authz-req+jwt, and turn jti tracking 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

  1. Send a valid signed object for lab-printer, built as in the previous lab. Consent shows photos.read.

Why it matters: a baseline that passes every check, so each failure below has one cause.

  1. Send the same object with &scope=openid%20photos.read%20photos.delete added to the URL. Consent still shows only photos.read.

Why it matters: "Only the object counts". If any part of the server read the unsigned URL value, the signature would mean nothing.

  1. 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-10 from this client's registration, aud, exp and nbf within policy, client_id matches, 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.

  1. In the same tool, run its built-in cases and note which step stops each one: a key not registered for this client, alg none, an aud naming another issuer, an expired exp, a future nbf, and an inner client_id that differs from the URL. Each stops at its own step with invalid_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.

  1. Sign a correct object for lab-tmp-vault with vault-1.pem and send it with client_id set to that client's ID. It succeeds. Present the same object with client_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.

  1. Sign a correct object for lab-printer that asks for photos.delete. The listener receives error=invalid_scope.

Why it matters: a valid signature proves who wrote the request. It does not make the request acceptable.

  1. Turn on Require signed request objects for lab-printer and open an ordinary unsigned authorization URL for it. The listener receives error=invalid_request.

Why it matters: "Errors and downgrades". Without this setting, signing is an option an attacker can simply decline.

Do today

  1. Refusal, not partial processing. Send a request parameter 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.

  1. No trusted return address, no redirect. Send only client_id and request, with no redirect_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.

  1. 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.

  1. Write the order as your own checklist. Using the CLAIMS you built in the previous lab, walk the lesson's seven steps by hand: which client, which key list you would use, then aud against $ISSUER, exp and nbf against date +%s, the inner client_id against the URL, and only then the scope and return address.

Break it

These run once G12 exists.

  1. Send one of your client assertions in request. It is refused because its typ is not oauth-authz-req+jwt. The two kinds of JWT you sign stay separate.

  2. Send an object that fails validation and leave redirect_uri out 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 the redirect_uri inside 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

  1. Delete lab-tmp-vault in OAuth > Clients and remove vault-1.pem and vault-1.pub.pem.

  2. 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.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab