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.
- G11 PAR
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
Use the
lab-printerclient and shell variables from See what a browser-carried authorization request exposes.Find
lab-printer-appin OAuth > Clients. If it does not exist, create it with the Native or desktop application preset: public, grantsauthorization_codeandrefresh_token, redirect URIhttp://127.0.0.1:8765/callback, scopesopenid profile offline_access photos.read. Then runexport APP_ID=<its client_id>.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
Find the endpoint in the tenant's metadata.
GET$ISSUER/.well-known/oauth-authorization-server
Open in console
GET $ISSUER/.well-known/oauth-authorization-serverExpect 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.
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.
Read the response. The value is the
urn:ietf:params:oauth:request_uri:prefix followed by a long random part, andexpires_inequals 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.
Push as the public client
lab-printer-app, with-d client_id=$APP_IDand no-u. Expect201.
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.
Send a push whose two client names disagree: the Basic header for
lab-printerandclient_id=$APP_IDin the body. Expect401 {"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.
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
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.
Send the wrong method.
GET$ISSUER/oauth/par
Open in console
GET $ISSUER/oauth/parThe answer is 405 with Allow: POST, the rule the lesson's error table states for this endpoint.
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.
Write the push you would send as a raw request, using the lesson's example as the model: the request line, the
Authorization: Basicheader,Content-Type: application/x-www-form-urlencodedand 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.
Push with a wrong secret. Expect
401 invalid_client, and Auditoauth.parrejectedinvalid_client. Compare this with the earlier lab's Break it, where the same mistake surfaced only after Ava had approved.Include
-d request_uri=urn:ietf:params:oauth:request_uri:xin the push. Expect400 invalid_request: a push may not point to another stored request.Omit
code_challengeforlab-printer, which requires PKCE. Expect400 invalid_requestas 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_endpointandrequire_pushed_authorization_requestsin discovery,oauth.parAudit 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.