Skip to content
Go to Dashboard

Using Pushed Authorization Requests (PAR) for Persona Authentications

The standard OIDC authorization flow is client-initiated: every parameter, including identity claims like given_name, family_name, and birthdate, is packed into the /authorize URL the browser is redirected to. Data passed as query parameters can leak into browser history, Referer headers, and server logs. A Pushed Authorization Request (PAR) avoids that by moving those parameters to a backend call instead.

This article assumes you’re already familiar with the OIDC flow Persona Authentications implements; see Understanding OpenID Connect (OIDC) for Persona Authentications if you need that background first.

What PAR is

PAR is an OAuth 2.0 / OIDC extension. Instead of sending authorization parameters to the browser-facing /authorize URL, the client first pushes those parameters directly to the authorization server over a backend, client-authenticated POST. The server stores them and returns a short-lived, single-use request_uri handle. The client then sends the user’s browser to /authorize with just client_id and that handle.

Persona uses PAR to securely prefill data on the inquiry it creates for an authentication, and to move responsibility for starting the flow from the browser to your server.

DetailValue
EndpointPOST https://authenticate.withpersona.com/authenticate/oidc/par
request_uri formaturn:ietf:params:oauth:request_uri:<random> (treat as opaque)
TTL90 seconds
Single-useConsumed the moment it’s redeemed at /authorize
At restThe stored authorization parameters are encrypted
Tip

PAR works whether or not your authentication template has “Require PAR” enabled. That setting only controls whether Persona rejects an /authorize call that skips the push step; it doesn’t gate whether PAR itself is available.

Before you start: map your fields

In your authentication template, configure query_params_to_inquiry_field_mapping to map each attribute you plan to push to the corresponding field on your inquiry template. A claim with no mapping is rejected when you try to create the pushed request, and every mapped field has to actually exist on the inquiry template version, or the template won’t publish.

If you intend to use verified claims instead of plain prefill data, skip ahead to that article; verified claims replace the request body shown below with a claims parameter and have their own field mapping.

Step 1: push the request

POST https://authenticate.withpersona.com/authenticate/oidc/par
Content-Type: application/x-www-form-urlencoded

client_id=<env_token>_<client_token>
client_secret=<secret>
response_type=code
redirect_uri=https://your.app/callback
scope=openid
state=<opaque_csrf_value>
login_hint=john@example.com
name_first=John
name_last=Smith
birthdate=1990-07-12

Requirements, all enforced by the server:

  • Content-Type must be application/x-www-form-urlencoded. Anything else returns 400 invalid_request.
  • Client authentication depends on your template’s token endpoint auth method. The example above uses client_secret_post (id and secret in the body). For client_secret_basic, omit them from the body and send HTTP Basic auth instead.
  • login_hint is required unless your template has account creation deferred. It’s the identifier Persona uses to find or create the account the pushed data is written onto. Omitting it returns Params login_hint is missing.
  • birthdate uses YYYY-MM-DD.

A successful push returns 201 Created with Cache-Control: no-store:

{
  "request_uri": "urn:ietf:params:oauth:request_uri:ab12…",
  "expires_in": 90
}

Step 2: redeem the request_uri

Redirect the user’s browser here with only client_id and request_uri. None of the other parameters travel in the URL; they were already pushed in Step 1.

GET https://authenticate.withpersona.com/authenticate/oidc/authorize
      ?client_id=<env_token>_<client_token>
      &request_uri=urn:ietf:params:oauth:request_uri:ab12…

Persona redeems the request, checks the stored client_id matches, finds or creates the account, and creates the inquiry with the pushed attributes. The browser then continues into the hosted inquiry flow. This is the last PAR-specific step; nothing downstream needs to know PAR was used.

The rest of the flow is unchanged

Once the request_uri is redeemed, the flow is identical to the standard authorization code flow:

  1. The user completes the inquiry and Persona redirects back to your redirect_uri with ?code=<auth_code>&state=<...>. Verify state matches what you sent.
  2. Exchange the code at /token for { id_token, access_token, expires_in, ... }.
  3. Optionally call /userinfo with the access_token for additional account-backed claims.

Where to go from here

How to trigger Persona identity verification within Okta using the Persona IDV integration is a concrete example of an integration that requires PAR.

Was this article helpful?
Thanks for the feedback. It helps us improve these docs.