Pushed Authorization Requests (PAR)
Pushed Authorization Requests (PAR) is an OAuth 2.0 extension defined in RFC 9126 that moves authorization parameters from the browser URL to a server-to-server POST request. This prevents parameter tampering, reduces URL length, and hides sensitive data (scopes, redirect URIs, PKCE challenges) from the browser address bar.
Why PAR matters
Section titled “Why PAR matters”In a standard authorization code flow, all parameters are visible in the browser URL:
/connect/authorize?client_id=my-app&response_type=code&scope=openid+profile+email &redirect_uri=https://app.example.com/callback&state=abc123 &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256An attacker with browser access can:
- Inspect parameters (leaking scopes, redirect URIs, client IDs)
- Tamper with parameters (modifying scopes, injecting malicious redirect URIs)
- Trigger URL truncation (complex requests exceed URL length limits)
PAR eliminates all three risks by pushing parameters server-side first.
How it works
Section titled “How it works”sequenceDiagram
participant BFF as BFF Server
participant IdP as OpenIddict Server
participant Browser
BFF->>IdP: POST /connect/par<br/>(client_id, client_secret,<br/>response_type, redirect_uri,<br/>scope, state, code_challenge)
IdP-->>BFF: 201 Created<br/>{ request_uri, expires_in }
BFF->>Browser: 302 Redirect<br/>/connect/authorize?client_id=...&request_uri=...
Browser->>IdP: GET /connect/authorize?client_id=...&request_uri=...
Note over IdP: Resolves request_uri<br/>to original parameters
IdP-->>Browser: Login page → Authorization code
-
Push parameters — The BFF (or any confidential client) POSTs all authorization parameters to
/connect/parwith client authentication. -
Receive request_uri — The server validates the request, stores the parameters, and returns an opaque
request_uri(valid for a short time). -
Redirect with request_uri — The browser is redirected to
/connect/authorizewith onlyclient_idandrequest_uri— no sensitive parameters in the URL. -
Normal flow continues — The authorization server resolves the
request_uri, authenticates the user, and issues an authorization code as usual.
Configuration
Section titled “Configuration”Server-side
Section titled “Server-side”The PAR endpoint (/connect/par) is always available — registered
automatically by AddGranitOpenIddict(). No additional
server configuration is needed for optional PAR.
To require PAR for all authorization code flows (reject direct requests
without a request_uri):
{ "OpenIddict": { "RequirePar": true }}BFF integration
Section titled “BFF integration”Enable PAR on a frontend to automatically push parameters before redirecting:
{ "Bff": { "Frontends": [ { "Name": "admin", "UsePushedAuthorizationRequests": true } ] }}When enabled, the BFF login flow:
- POSTs all authorization parameters to
/connect/par - Receives
request_urifrom the server - Redirects the browser with only
client_id+request_uri - Falls back to direct parameters if PAR fails (graceful degradation)
PAR response format
Section titled “PAR response format”A successful PAR request returns 201 Created with a JSON body:
{ "request_uri": "urn:openiddict:params:request:abc123def456", "expires_in": 60}| Field | Type | Description |
|---|---|---|
request_uri | string | Opaque reference to the stored authorization parameters |
expires_in | int | Seconds until the request_uri expires (typically 60s) |
Error handling
Section titled “Error handling”| Status | Error | Cause |
|---|---|---|
400 Bad Request | invalid_request | Missing or malformed parameters |
400 Bad Request | invalid_client | Missing or invalid client authentication |
400 Bad Request | invalid_redirect_uri | Redirect URI not registered for this client |
403 Forbidden | insufficient_scope | Client lacks ept:pushed_authorization permission |
Traditional vs PAR flow comparison
Section titled “Traditional vs PAR flow comparison”Browser URL (visible, tamperable):/connect/authorize ?client_id=my-app &response_type=code &scope=openid profile email offline_access &redirect_uri=https://app.example.com/callback &state=xyzSecretState123 &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256All parameters visible in browser history, logs, and referrer headers.
Browser URL (opaque, untamperable):/connect/authorize ?client_id=my-app &request_uri=urn:openiddict:params:request:abc123Parameters pushed server-side. Only the opaque request_uri is in the URL.
Security considerations
Section titled “Security considerations”| Threat | Mitigation |
|---|---|
| Parameter tampering in URL | Parameters are server-side, not in URL |
| PII leakage via browser history | Only opaque request_uri in URL |
| URL truncation (long scopes) | Parameters sent via POST body |
Replay of request_uri | Short-lived (typically 60 seconds), single-use |
| Unauthorized PAR requests | Client authentication required on /connect/par |
| Man-in-the-browser | Combined with PKCE, provides defense in depth |
FAPI 2.0 compliance
Section titled “FAPI 2.0 compliance”PAR is mandatory in the FAPI 2.0 Security Profile alongside DPoP and PKCE. Industries requiring FAPI 2.0 include:
- Open Banking (PSD2, UK Open Banking)
- Healthcare (HL7 SMART on FHIR)
- Government (eIDAS, Digital Identity)
- Insurance (ACORD standards)
See also
Section titled “See also”- Private Key JWT Authentication — eliminate shared secrets
- DPoP (Proof-of-Possession) — bind tokens to cryptographic keys
- DPoP Resource Server Validation — validate DPoP proofs on any IdP
- FAPI 2.0 Security Profile — full conformance checklist
- OIDC Client Primitives — typed requests, discovery, PKCE
- OIDC Server Configuration — endpoints and flows overview
- BFF Configuration — frontend options reference
- Configuration Reference —
RequireParoption