Using Pushed Authorization Requests (PAR) with OpenIddict
/ 9 min read
Introduction
Pushed Authorization Requests (PAR) is an extension to OAuth 2.0, as defined in RFC 9126. It was created to address, among other things, certain security risks with user interactive OAuth 2.0 flows such as the Authorization Code flow.
In this article, I will discuss the issues PAR addresses and demonstrates how you can enable PAR when using OpenIddict. I’ll also show how you can configure your OpenIddict clients for PAR using AdminUI for OpenIddict.
Understanding the problem
As mentioned in the introduction, PAR addresses security concerns when using user interactive OAuth 2.0 flows such as the Authorization Code flow. To better understand the potential security risks, let’s look at the OAuth 2.0 Authorization Code flow in a web application scenario.

- The user initiates an authentication flow.
- The client makes a request to the authorization endpoint.
- The user authenticates on the Authorization Server…
- …and consents to the Client accessing the secure resource.
- The Authorization Server redirects to the
redirect_uri
on the Client, passing the authorizationcode
. - The Client makes a request to the Authorization Server to exchange the authorization
code
for an Access Token. - The Authorization Server returns the
access_token
and, optionally,refresh_token
. - The Client uses the
access_token
to access the protected resources on the Resource Server.
As you can see, the code exchange (6 and 7) is happening over the secure back channel, but the initial request to the authorization endpoint (2) occurs over the front channel.
Let’s look at the request to the authorization endpoint.
GET /connect/authorize? response_type=token& client_id=s6BhdRkqt3& redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb& scope=openid%20profile%20cms%3Aread%20cms%3Awrite& state=af0ifjsldkj& nonce=n-0S6_WzA2Mj HTTP/1.1Host: server.example.com
Since this request occurs on the front channel, it raises the following concerns:
- Request Integrity and Authentication: While OAuth has mechanisms to detect tampering, they’re reactive rather than preventive. An attacker could, for example, modify scope parameters or authorization context during transit, fundamentally changing the request’s intent.
- Data Confidentiality: Even with HTTPS, request parameters in standard OAuth flows are exposed in multiple places. Query parameters end up in web server logs, browser histories, and can leak through referrer headers. Any sensitive data included in the authorization request becomes vulnerable to exposure through these various channels.
- Request Size Constraints: Modern OAuth implementations often require detailed authorization parameters, leading to long URLs. This could create problems with browser limitations and proxy servers. As applications require more fine-grained permissions and additional context in their authorization requests, these size limitations become increasingly problematic.
PAR addresses these concerns by sending those sensitive parameters over the back channel. When using PAR, the authorization flow is changed as follows:

This flow looks similar to the previous one, but an additional couple of steps were added. As can be seen in steps (2) and (3) in the diagram, before redirecting the user to the authorization endpoint, the Client makes a secure request over the back channel to the PAR endpoint. This request contains the sensitive information such as the redirect_uri
, scope
, etc.
The Authorization Server responds with a payload that contain a request_uri
. This request_uri
is a single-use reference to the respective authorization parameters and is used by the Authorization Server to look up those parameters during the authorization request.
The request to the authorization endpoint (4) contains this request_uri
, rather than the sensitive parameters. As mentioned before, the Authorization Server internally uses this request_uri
to extract the parameters such as the redirect_uri
and scope
.
Creating a demo scenario
Let’s look at all of this in action by creating a small demo application with OpenIddict.
OpenIddict introduced PAR support in version 6.1.0. To enable PAR support, you can review the OpenIddict documentation on Pushed Authorization Requests. For the purpose of this blog post, I’ll be using AdminUI for OpenIddict - they added support for PAR in version 3.0.0 of their NuGet package.
For the sample application, I created a new project using version 3.0.0 of the AdminUI All-in-one template and followed the guidance in their documentation on setting up a new project. I also added an ASP.NET Core Razor Pages project for the client application.
Next, I added a new Web App client application in AdminUI. The Callback URL and Logout URL uses the URL for the previously created Razor Pages app, and uses the default CallbackPath
and SignedOutCallbackPath
properties for the OpenID Connection authentication provider - callback-oidc
and signout-callback-oidc
respectively.

Once the client is created, I configured OpenID Connect authentication for my client application, using the Client ID of the client I just created in AdminUI and the URL of my OpenIddict server (https://localhost:5003
) as the Authority
.
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5003"; options.ClientId = "par-demo-client";
options.ResponseType = OpenIdConnectResponseType.Code; });
Analyzing the non-PAR authentication flow
At this point, I have not enabled PAR support for the client application yet. Let’s run through a normal authentication scenario and view the requests in the browser.

You can see the request to the /authorize
endpoint contains the query parameters for client_id
, redirect_uri
, scope
, etc. At this time, our request is exposed to the previously mentioned vulnerabilities.
Enabling PAR
Let’s enable PAR support for our application. First, we must update our OpenIddict configuration to enable the PAR endpoint. We can do this by calling the SetPushedAuthorizationEndpointUris()
method in our server configuration.
services.AddOpenIddict() .AddCore(options => { // ...omitted for brevity }) .AddClient(options => { // ...omitted for brevity }) .AddServer(options => { options.DisableAccessTokenEncryption(); options.SetAuthorizationEndpointUris("connect/authorize") .SetEndSessionEndpointUris("connect/logout") .SetTokenEndpointUris("connect/token") .SetUserInfoEndpointUris("connect/userinfo") .SetPushedAuthorizationEndpointUris("connect/par");
// ...omitted for brevity }) .AddValidation(options => { // ...omitted for brevity });
Next, we need to enable PAR support for our OAuth client. Once again, I’ll use AdminUI to edit the client and enable PAR support from the Advanced > PAR tab.

We have the option to simply enable PAR support, or make it a requirement. For now, I will just enable it, allowing clients to opt out of it.
The final step is to enable PAR in our client application. This is done via the PushedAuthorizationBehavior
property of the OpenIdConnectOptions
class. This property takes an enum value which is declared as follows:
public enum PushedAuthorizationBehavior{ /// <summary> /// Use Pushed Authorization (PAR) if the PAR endpoint is available in the identity provider's discovery document or the explicit <see cref="T:Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration" />. This is the default value. /// </summary> UseIfAvailable, /// <summary> /// Never use Pushed Authorization (PAR), even if the PAR endpoint is available in the identity provider's discovery document or the explicit <see cref="T:Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration" />. /// If the identity provider's discovery document indicates that it requires Pushed Authorization (PAR), the handler will fail. /// </summary> Disable, /// <summary> /// Always use Pushed Authorization (PAR), and emit errors if the PAR endpoint is not available in the identity provider's discovery document or the explicit <see cref="T:Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration" />. /// </summary> Require,}
The default value is UseIfAvailable
so, if we do not make any code modifications, the OpenID Connect client will detect that PAR is enabled on our OpenIddict server and use it. We can therefore leave our client application as-is, or make PAR a requirement as per the code snippet below.
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5003"; options.ClientId = "par-demo-client";
options.ResponseType = OpenIdConnectResponseType.Code; options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Require; });
Analyzing the PAR authentication flow
If we log in to our demo client application again and review the requests made in the browser, we can see that the call to the /authorize
endpoint looks very different this time.

The request only contains the client_id
and request_uri
parameters. Potentially sensitive parameters, such as the redirect_uri
and scope
was not exposed.
Since we are running in our local development environment, we can review the logs of our OpenIddict server as well.

- An initial request was made to the PAR endpoint, passing all the sensitive parameters. Since this call is made on the back channel, these parameters are not exposed.
- The PAR endpoint processed the request and returned a response payload containing the
request_uri
. - The subsequent request to the authorization endpoint only contains the
client_id
and therequest_id
parameters - as we also observed in the previous screenshot of the requests made in the browser.
Conclusion
In this blog post, I have you a brief overview of the Pushed Authentication Requests OAuth 2.0 extension and the problems it tries to address. I created a sample application using OpenIddict to demonstrate the differences between authorization requests made without PAR support enabled, and then contrasted that with requests made with PAR support enabled.
The source code for the demo application can be found at https://github.com/jerriepelser-blog/openiddict-par-support.