OAuth 2.0 — Pushed Authorization Requests (PAR)
Pushed Authorization Requests (RFC 9126) flip the normal authorization flow: instead of encoding the full authorization request in the browser redirect URL, the client POSTs the request directly to the AS server-to-server first and receives a short-lived request_uri. The browser redirect carries only the client_id and request_uri. This prevents tampering (request params never touch the browser), supports arbitrarily large requests (e.g., Rich Authorization Requests), and gives the AS early validation before the user ever sees a prompt.
The client POSTs the full authorization request to the AS's PAR endpoint server-to-server, before involving the browser. The AS validates the request immediately and returns a short-lived request_uri that acts as a pointer to the stored request. The client authenticates in the same way it would at the token endpoint.
• RFC 9126 §2.1: The PAR endpoint MUST require client authentication for confidential clients.
• RFC 9126 §2.2: The AS responds with 201 Created (not 200) when the request is successfully stored.
• expires_in=60: The request_uri must be used within 60 seconds or it expires. This prevents stale authorization requests.
• Scopes (`read:profile`, `write:reports`) are application-defined permission strings that describe exactly what access is being requested on the user's behalf. The AS validates that this client is permitted to request these scopes before issuing the request_uri.
• PAR pairs naturally with JAR (JWT-Secured Authorization Requests, RFC 9101) — the pushed body can be a signed JWT for non-repudiation of the authorization request itself.
• PAR is required by FAPI 2.0 Security Profile for high-security financial-grade APIs.
Step 1: POST /par (push auth request)
application/x-www-form-urlencoded
Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
response_type=code&client_id=s6BhdRkqt3&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=read:profile+write:reports&state=af0ifjsldkj&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
Construction Steps
Traditional authorization code flow puts ALL parameters in the browser
redirect URL:
https://as.example.com/authorize
?response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https://app.example.com/callback
&scope=read:profile+write:reports
&state=af0ifjsldkj
&code_challenge=E9Melhoa... ← visible in browser history
&code_challenge_method=S256 and server logs
Problems with URL-based requests:
• Visible in browser history, server logs, Referer headers
• Can be tampered with before the AS receives them
• URL length limits prevent large payloads (e.g., RAR authorization_details)
• No AS validation until the user reaches the AS
PAR solution: POST the full request directly to the AS first.On receiving the PAR request, the AS validates everything NOW—before the user ever sees a browser prompt: • Client authentication (Basic auth header) • client_id is registered • redirect_uri exactly matches a registered URI • response_type and requested scopes (read:profile, write:reports) are permitted for this client • code_challenge is present (if PKCE is required) If any check fails → 400 error response to the client. The browser is never involved in a failed request — no confusing error pages for the user, and no partial state on the AS.
request_uri = "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY" RFC 9126 §2.2: The request_uri: • Format is at the AS's discretion (urn:ietf:params:oauth:request_uri: is conventional) • Is a single-use opaque reference to the stored request object • Expires in `expires_in` seconds (typically 30–60 seconds) • Is bound to the client_id that created it The stored request object includes ALL original parameters. The browser URL in the next step carries ONLY client_id + request_uri.
Signature Base String
Full authorization request parameters posted server-to-server
Signing Key
Client authenticates with client_secret_basic (same as token endpoint)Signature Output
request_uri = urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY (expires in 60s)