PAR
PAR (RFC 9126) is an OAuth 2.0 security extension that allows clients to push authorization request parameters directly to the authorization server before redirecting the user, providing enhanced security and privacy.
Overview
- RFC: RFC 9126 - OAuth 2.0 Pushed Authorization Requests
- Status: Fully Implemented
- Endpoint:
POST /as/par
Why Use PAR?
Security Benefits
-
Parameter Tampering Prevention
- Authorization parameters are sent via secure backend channel
- Parameters cannot be modified by user or intermediaries
- Request integrity is guaranteed
-
Privacy Protection
- Sensitive parameters not exposed in browser history
- No leakage through referrer headers
- Prevents surveillance of authorization requests
-
URL Length Limitations
- Avoids browser/server URL length limits
- Enables complex requests with many parameters
- Supports large request objects (JAR)
-
Client Authentication
- PAR endpoint can require client authentication
- Prevents unauthorized authorization requests
- Reduces phishing risks
Use Cases
- Financial Services (FAPI compliance)
- Healthcare (HIPAA-compliant OAuth flows)
- Enterprise (complex authorization with many parameters)
- Mobile Apps (secure authorization from native apps)
How PAR Works
Flow Overview
- Client pushes parameters: Client sends authorization parameters to PAR endpoint
- Server stores parameters: Server validates and stores parameters (10-minute TTL)
- Server returns request_uri: Server responds with a unique
request_uri - Client redirects user: Client redirects user to authorization endpoint with
request_uri - Server retrieves parameters: Authorization endpoint retrieves stored parameters
- Normal flow continues: Authorization proceeds as normal
API Reference
PAR Endpoint
POST /as/par
Request Format
POST /as/par HTTP/1.1Content-Type: application/x-www-form-urlencodedAuthorization: Basic <base64(client_id:client_secret)>
client_id=my_client_id&response_type=code&redirect_uri=https://myapp.example.com/callback&scope=openid+profile+email&state=abc123&nonce=xyz789&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256Request Parameters
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Client identifier |
response_type | Yes | OAuth response type (e.g., code) |
redirect_uri | Yes | Client’s registered redirect URI |
scope | Yes | Requested scopes (space-separated) |
state | No | Opaque value for CSRF protection |
nonce | No | Nonce for ID token binding (OIDC) |
code_challenge | No | PKCE code challenge |
code_challenge_method | No | PKCE method (must be S256) |
Success Response
Status: 201 Created
{ "request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c", "expires_in": 600}| Field | Type | Description |
|---|---|---|
request_uri | string | Unique URN identifying the stored request |
expires_in | number | Lifetime in seconds (default: 600 = 10 minutes) |
Authorization Endpoint with PAR
GET /authorize
GET /authorize ?request_uri=urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c &client_id=my_client_idHost: auth.example.com| Parameter | Required | Description |
|---|---|---|
request_uri | Yes | The request URI from PAR response |
client_id | Yes | Must match client_id from PAR request |
Usage Examples
Basic PAR Flow
Step 1: Push Authorization Request
curl -X POST https://auth.example.com/as/par \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=my_client_id" \ -d "response_type=code" \ -d "redirect_uri=https://myapp.example.com/callback" \ -d "scope=openid profile email" \ -d "state=abc123" \ -d "nonce=xyz789"Response:
{ "request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c", "expires_in": 600}Step 2: Redirect User to Authorization Endpoint
const authUrl = new URL('https://auth.example.com/authorize');authUrl.searchParams.set('request_uri', 'urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c');authUrl.searchParams.set('client_id', 'my_client_id');
window.location.href = authUrl.toString();PAR with PKCE
# Generate PKCE challengeCODE_VERIFIER=$(openssl rand -base64 96 | tr -d '\n' | tr '/+' '_-' | tr -d '=')CODE_CHALLENGE=$(echo -n $CODE_VERIFIER | openssl dgst -binary -sha256 | openssl base64 | tr -d '\n' | tr '/+' '_-' | tr -d '=')
# Push authorization request with PKCEcurl -X POST https://auth.example.com/as/par \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=my_public_client" \ -d "response_type=code" \ -d "redirect_uri=https://myapp.example.com/callback" \ -d "scope=openid profile" \ -d "code_challenge=$CODE_CHALLENGE" \ -d "code_challenge_method=S256"JavaScript Client Library
async function authorizeWithPAR(config: { clientId: string; redirectUri: string; scope: string; state: string; nonce: string;}) { // Step 1: Push authorization request const parResponse = await fetch('https://auth.example.com/as/par', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: config.clientId, response_type: 'code', redirect_uri: config.redirectUri, scope: config.scope, state: config.state, nonce: config.nonce, }), });
const { request_uri } = await parResponse.json();
// Step 2: Redirect to authorization endpoint const authUrl = new URL('https://auth.example.com/authorize'); authUrl.searchParams.set('request_uri', request_uri); authUrl.searchParams.set('client_id', config.clientId);
window.location.href = authUrl.toString();}Implementation Details
Request URI Format
- Scheme:
urn:ietf:params:oauth:request_uri: - Identifier: Cryptographically secure random string
- Example:
urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c
Security Features
- Single-Use: Request URIs are deleted immediately after retrieval
- Short Lifetime: Default 10 minutes (600 seconds)
- Client ID Matching:
client_idin authorization request must match PAR request - Secure Storage: Parameters stored in KV with automatic expiration
Comparison: Traditional vs PAR
Traditional Authorization Request
GET /authorize ?response_type=code &client_id=my_client_id &redirect_uri=https://myapp.example.com/callback &scope=openid+profile+email &state=abc123Issues:
- Parameters exposed in URL
- Visible in browser history
- Limited by URL length
- Can be tampered with
PAR Authorization Request
POST /as/par → {request_uri}
GET /authorize?request_uri=urn:ietf:...&client_id=my_client_idBenefits:
- Parameters sent securely via POST
- Not visible in browser history
- No URL length limitations
- Cannot be tampered with
Error Responses
{ "error": "invalid_request", "error_description": "client_id is required"}{ "error": "invalid_request", "error_description": "Invalid or expired request_uri"}{ "error": "invalid_request", "error_description": "client_id mismatch"}Compliance
- FAPI 2.0: PAR is mandatory for FAPI 2.0 compliance
- OAuth 2.1: PAR is part of OAuth 2.1 draft
- OpenID Connect: Compatible with all OIDC flows
Discovery Metadata
PAR support is advertised in the OpenID Provider metadata:
{ "pushed_authorization_request_endpoint": "https://auth.example.com/as/par", "require_pushed_authorization_requests": false}