Skip to content

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

Why Use PAR?

Security Benefits

  1. Parameter Tampering Prevention

    • Authorization parameters are sent via secure backend channel
    • Parameters cannot be modified by user or intermediaries
    • Request integrity is guaranteed
  2. Privacy Protection

    • Sensitive parameters not exposed in browser history
    • No leakage through referrer headers
    • Prevents surveillance of authorization requests
  3. URL Length Limitations

    • Avoids browser/server URL length limits
    • Enables complex requests with many parameters
    • Supports large request objects (JAR)
  4. 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

  1. Client pushes parameters: Client sends authorization parameters to PAR endpoint
  2. Server stores parameters: Server validates and stores parameters (10-minute TTL)
  3. Server returns request_uri: Server responds with a unique request_uri
  4. Client redirects user: Client redirects user to authorization endpoint with request_uri
  5. Server retrieves parameters: Authorization endpoint retrieves stored parameters
  6. Normal flow continues: Authorization proceeds as normal

API Reference

PAR Endpoint

POST /as/par

Request Format

POST /as/par HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Authorization: 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=S256

Request Parameters

ParameterRequiredDescription
client_idYesClient identifier
response_typeYesOAuth response type (e.g., code)
redirect_uriYesClient’s registered redirect URI
scopeYesRequested scopes (space-separated)
stateNoOpaque value for CSRF protection
nonceNoNonce for ID token binding (OIDC)
code_challengeNoPKCE code challenge
code_challenge_methodNoPKCE method (must be S256)

Success Response

Status: 201 Created

{
"request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c",
"expires_in": 600
}
FieldTypeDescription
request_uristringUnique URN identifying the stored request
expires_innumberLifetime 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_id
Host: auth.example.com
ParameterRequiredDescription
request_uriYesThe request URI from PAR response
client_idYesMust match client_id from PAR request

Usage Examples

Basic PAR Flow

Step 1: Push Authorization Request

Terminal window
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

Terminal window
# Generate PKCE challenge
CODE_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 PKCE
curl -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

  1. Single-Use: Request URIs are deleted immediately after retrieval
  2. Short Lifetime: Default 10 minutes (600 seconds)
  3. Client ID Matching: client_id in authorization request must match PAR request
  4. 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=abc123

Issues:

  • 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_id

Benefits:

  • 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
}

References