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

Register installations from a trusted software statement

Trust a software statement issuer and register installations whose name, redirect URI and scope come from the statement. Today, classify the claims and test a self-asserted name.

PlannedUses your lab tenant

The lesson

Builds on: Registering a client dynamically.

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 shell variables and, once G9 exists, the IAT initial access token from Register a review-site client through the registration API.

  2. Create the printing company's statement key, used for nothing else. This runs today.

openssl ecparam -name prime256v1 -genkey -noout -out printer-statements-2026.pem
openssl ec -in printer-statements-2026.pem -pubout -out printer-statements-2026.pub.pem
  1. Planned (G9, G56): in OAuth > Registration > Trusted statement issuers, add the issuer https://printer.example with the public key printer-statements-2026.pub.pem (key ID printer-statements-2026) and the allowed software_id 7e3c1f52-9a4d-4b8e-a6f0-2d91c5e8b371.

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. Build the statement claims (this part runs today), then sign them as ES256 with printer-statements-2026.pem and the header {"alg":"ES256","kid":"printer-statements-2026","typ":"JWT"}. Save the result as SS.

STATEMENT=$(jq -nc --argjson iat $(date +%s) '{iss:"https://printer.example", iat:$iat, software_id:"7e3c1f52-9a4d-4b8e-a6f0-2d91c5e8b371",
  software_version:"4.2.0", client_name:"Photo Printer", client_uri:"https://printer.example",
  redirect_uris:["http://127.0.0.1:8765/callback"], grant_types:["authorization_code","refresh_token"],
  response_types:["code"], token_endpoint_auth_method:"none", scope:"photos.read"}')
echo "$STATEMENT" | jq .

Why it matters: "Software statements". A key the tenant already trusts vouches for these values, and that key signs statements and nothing else.

  1. Register an installation with the statement plus a conflicting plain value.

curl -s "$ISSUER/oauth/register" -H "Authorization: Bearer $IAT" -H 'Content-Type: application/json' \
  -d "{\"software_statement\":\"$SS\",\"client_name\":\"Something Else\"}" | jq '{client_id, client_name, software_id, redirect_uris}'

client_name is "Photo Printer".

Why it matters: where a trusted statement and the plain request disagree, the statement wins.

  1. Register without a statement, with client_uri https://printer.example and a redirect URI on another host. The tenant flags the mismatch, or refuses it when Require matching hosts is on.

Why it matters: "Deciding what to accept". The tenant weighs the request as a whole, not field by field.

  1. Register without token_endpoint_auth_method. The response shows client_secret_basic and includes a secret.

Why it matters: "What a request can say". Defaults can hand a client a credential it was never designed to hold, so a careful client states every field.

Do today

  1. Run Planned walkthrough step 1's jq command. For each claim, write one label: "decides where codes go", "decides how the client gets tokens", "decides how it authenticates", "shown to people" or "identifies the software". Mark which labels the lesson says are dangerous when self-asserted.

  2. Check the request as a whole, as the lesson suggests: compare the scheme and host of client_uri with each redirect URI.

echo "$STATEMENT" | jq -r '.client_uri, .redirect_uris[]' | sed -E 's#^([a-z]+://[^/]+).*#\1#'

The loopback redirect URI does not share the website's host. In a real registration, that would deserve a closer look.

  1. See a self-asserted name on a real consent screen. In OAuth > Clients, create lab-tmp-statement with the Web application preset, redirect URI http://127.0.0.1:8765/callback and scope openid. Run a code flow for it and look at the consent screen: it shows exactly the name typed into the form. Nothing checked it, which is why a server must treat a registration's client_name as a claim unless something it trusts vouches for it.

  2. Confirm that the tenant assigns the client ID: the form has no field for choosing one.

Break it

These run once G9 and G56 exist.

  1. Sign the statement with a key that is not registered for https://printer.example. Expect 400 invalid_software_statement.

  2. Sign a correct statement whose iss is an issuer the tenant has not added. Expect 400 unapproved_software_statement.

Check your work

Today, Audit shows tenant.oauth.clients.create for lab-tmp-statement, its oauth.authorize events, and tenant.oauth.clients.delete after Cleanup. Once the gaps close, Audit shows oauth.register succeeded with the software_id and statement issuer recorded, and the two Break it refusals.

Cleanup

  1. Delete lab-tmp-statement in OAuth > Clients.

  2. Once the gaps close, remove the trusted statement issuer and the clients you registered.

  3. Delete printer-statements-2026.pem and its public file.

Missing infrastructure

  • G9: the registration endpoint, trusted statement issuers per tenant, per-field acceptance rules (accept, replace or reject), and a request-as-a-whole check such as Require matching hosts.

  • G56: public keys for statement issuers and for installations that register a key by value.

  • Once these 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