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

Push an authorization request from the back end

Push lab-printer's authorization request with client authentication, read the request_uri and expires_in, and see bad pushes refused before any browser is involved.

PlannedUses your lab tenant

The lesson

Builds on: The problem PAR solves.

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.

Request console

Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.

Setup

  1. Use the lab-printer client and shell variables from See what a browser-carried authorization request exposes.

  2. Find lab-printer-app in OAuth > Clients. If it does not exist, create it with the Native or desktop application preset: public, grants authorization_code and refresh_token, redirect URI http://127.0.0.1:8765/callback, scopes openid profile offline_access photos.read. Then run export APP_ID=<its client_id>.

  3. Planned (G11): in OAuth > Flow policy, set Pushed authorization requests to Allowed and Request URI lifetime to 60 seconds. The proposed range is 10 to 600 seconds, matching the lesson's "a few seconds to ten minutes". Leave Require PAR off; the failure-cases lab turns it on.

Planned walkthrough

  1. Find the endpoint in the tenant's metadata.

GET$ISSUER/.well-known/oauth-authorization-server Open in console
GET $ISSUER/.well-known/oauth-authorization-server

Expect pushed_authorization_request_endpoint to be $ISSUER/oauth/par and require_pushed_authorization_requests to be false.

Why it matters: the client takes the address from metadata, under the name the lesson shows, rather than guessing it.

  1. Push the same parameters you used to put in the address bar.

btl-lab pkce     # export VERIFIER and CHALLENGE
btl-lab state    # export STATE
curl -si -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/par" \
  -d response_type=code -d client_id=$CLIENT_ID --data-urlencode redirect_uri=$REDIRECT \
  --data-urlencode "scope=openid photos.read" -d state=$STATE \
  -d code_challenge=$CHALLENGE -d code_challenge_method=S256

Expect HTTP/1.1 201, Cache-Control: no-cache, no-store and a body like {"request_uri":"urn:ietf:params:oauth:request_uri:<random>","expires_in":60}. Save it with export REQUEST_URI=<value>.

Why it matters: the client authenticated with the same Basic header it uses at the token endpoint, before anyone saw a page.

  1. Read the response. The value is the urn:ietf:params:oauth:request_uri: prefix followed by a long random part, and expires_in equals your Flow policy setting.

Why it matters: the reference is a name, not an address anyone can fetch, and its short lifetime is a tenant choice.

  1. Push as the public client lab-printer-app, with -d client_id=$APP_ID and no -u. Expect 201.

Why it matters: a public client still keeps its parameters out of the browser and gets a short URL, but the tenant cannot authenticate it here any more than at the token endpoint.

  1. Send a push whose two client names disagree: the Basic header for lab-printer and client_id=$APP_ID in the body. Expect 401 {"error":"invalid_client"}.

Why it matters: the body's client_id belongs to the authorization request and the header is a credential. A careful server refuses a push in which they name different clients.

  1. Open Audit and filter to oauth.par. Each push is attributed to an authenticated client, with no user involved.

Why it matters: the checks the earlier lab saw happen late, client authentication and request validation, now run first.

Do today

  1. Push today. The tenant names the endpoint but does not run it.

curl -si -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/par" -d response_type=code -d client_id=$CLIENT_ID

The answer is 501 {"error":"temporarily_unavailable","error_description":"This endpoint is not implemented yet."}. In Logs, find oauth.par rejected not_implemented.

  1. Send the wrong method.

GET$ISSUER/oauth/par Open in console
GET $ISSUER/oauth/par

The answer is 405 with Allow: POST, the rule the lesson's error table states for this endpoint.

  1. Read what the metadata promises.

curl -s "$ISSUER/.well-known/openid-configuration" | jq '{par: .pushed_authorization_request_endpoint, status: .btl_endpoint_status["/oauth/par"]}'

The result is null and "not_implemented". A client that reads metadata correctly will not attempt PAR against this tenant, and will not fall back silently either: it knows before it starts.

  1. Write the push you would send as a raw request, using the lesson's example as the model: the request line, the Authorization: Basic header, Content-Type: application/x-www-form-urlencoded and the form body. Mark which lines are client authentication and which belong to the authorization request.

Break it

These refusals run once G11 exists. Each happens before any browser is involved.

  1. Push with a wrong secret. Expect 401 invalid_client, and Audit oauth.par rejected invalid_client. Compare this with the earlier lab's Break it, where the same mistake surfaced only after Ava had approved.

  2. Include -d request_uri=urn:ietf:params:oauth:request_uri:x in the push. Expect 400 invalid_request: a push may not point to another stored request.

  3. Omit code_challenge for lab-printer, which requires PKCE. Expect 400 invalid_request as JSON, not a redirect.

Check your work

Once G11 exists, Audit shows oauth.par succeeded for lab-printer and lab-printer-app, with the client as actor, and the rejections from step 5 and Break it. Today, Logs show oauth.par rejected not_implemented and method_not_allowed from the Do today steps.

Cleanup

Nothing to remove. Unused request URIs expire on their own.

Missing infrastructure

  • G11: the PAR endpoint itself, with stored pushed requests that are single use, bound to the pushing client and named by a random reference. The tenant also needs Flow policy and per-client PAR settings, pushed_authorization_request_endpoint and require_pushed_authorization_requests in discovery, oauth.par Audit reasons for each refusal, and a push rate limit registered as a visible tenant usage policy. Once these exist, 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