Improve ASP.NET Core authentication using OAuth PAR and OpenID Connect

This article shows how an ASP.NET Core application can be authenticated using OpenID Connect and OAuth 2.0 Pushed Authorization Requests (PAR) RFC 9126. The OpenID Connect server is implemented using Duende IdentityServer. The Razor Page ASP.NET Core application authenticates using an OpenID Connect confidential client with PKCE and using the OAuth PAR extension. By using PAR, the client is authenticated first, if it is a confidential client and no parameters are passed in the front channel.

Code: https://github.com/damienbod/oidc-par-aspnetcore-duende

History

  • 2024-08-15 Updated to .NET 9, using default OIDC client handler

Note: The code in this example was created using the Duende example found here: https://github.com/DuendeSoftware/IdentityServer

By using Pushed Authorization Requests (PAR), the authentication flow security is improved. In ASP.NET Core using PAR, the application is authenticated on the trusted backchannel before sending any authentication request. The parameters are no longer sent in the URL reducing the risk by not sharing the parameters or prevent parameter pollution with redirect_uri injection. No parameters are shared in the front channel. The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR) RFC 9101 can also be used together with this to further improve the authentication security.

Overview

The OAuth PAR extension adds an extra step to the two step OpenID Connect client authentication code flow. OAuth Pushed Authorization Requests (PAR) extended flow has three steps:

  1. Client sends a HTTP request in the back channel with the authorization parameters and the client is authenticated first. The body of the request has the OpenID Connect code flow parameters. The server responds with the request_uri.
  2. The client uses the request_uri from the first step and authenticates. The server uses the flow parameters from the first request. As code flow with PKCE is used, the code is returned in the front channel.
  3. The client completes the authentication using the code flow in the back channel, standard OpenID Connect code flow with PKCE.

Duende IdentityServer setup

I used Duende IdentityServer to implement the standard. Any OpenID Connect server which supports the OAuth PAR standard can be used. It is very simple to support this using Duende IdentityServer. The RequirePushedAuthorization is set to true and PAR is active for this client. The rest of the client configuration is a standard OIDC confidential client using code flow with PKCE.

new Client[]
{
  new Client
  {
	ClientId = "web-par",
	ClientSecrets = 
	{ 
		new Secret("--your-secret--".Sha256()) 
	},

	RequirePushedAuthorization = true,

	AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,

	RedirectUris = 
	{ 
		"https://localhost:5007/signin-oidc" 
	},
	FrontChannelLogoutUri = 
		"https://localhost:5007/signout-oidc",
	PostLogoutRedirectUris = 
	{ 
		"https://localhost:5007/signout-callback-oidc" 
	},

	AllowOfflineAccess = true,
	AllowedScopes = { "openid", "profile" }
  }
};

ASP.NET Core OpenID Connect client

The ASP.NET Core client requests extra changes. An extra back channel PAR request is sent in the OpenID Connect events. The OIDC events needs to be changed compared to the standard core OIDC setup. I used the Duende.AccessTokenManagement.OpenIdConnect nuget package to implement this and updated the OIDC events using the ParOidcEvents class from the Duende examples. The setup uses the PAR events in the AddOpenIdConnect configuration which requires a HttpClient and the IDiscoveryCache interface from Duende.


services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.ExpireTimeSpan = TimeSpan.FromHours(8);
    options.SlidingExpiration = false;
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Authority = configuration["OidcDuende:Authority"];
    options.ClientId = configuration["OidcDuende:ClientId"];
    options.ClientSecret = configuration["OidcDuende:ClientSecret"];
    options.ResponseType = "code";
    options.ResponseMode = "query";
    options.UsePkce = true;

    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("offline_access");
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;
    options.MapInboundClaims = false;

    options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Require;

    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
});

See https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#openidconnecthandler-adds-support-for-pushed-authorization-requests-par

Notes

It is simple to use PAR and the it adds an improved authentication security with one extra request in the authentication flow. This should be used if possible. The standard can be used together with the OAuth JAR standard and even extended with the OAuth RAR.

Links

https://github.com/DuendeSoftware/IdentityServer

OAuth 2.0 Pushed Authorization Requests (PAR) RFC 9126

OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR) RFC 9101

OAuth 2.0 Rich Authorization Requests (RAR) RFC 9396

4 comments

  1. Dominick Baier's avatar

    Hi Damien,

    this looks very much like our sample that we published the other day. You make it look like your own original work.

    Am I missing the source attribution somewhere?

    thanks
    Dominick

    1. damienbod's avatar

      Hi Dominick, I have linked the src (your repo) in the readme, in the blog here and in the class which I created from the example saying I created this from your example.

      Greetings Damien

      1. damienbod's avatar

        Also added another explicit reference to your example at the top of the blog. I hope this is the correct attribution you require. Greetings Damien

  2. […] Improve ASP.NET Core authentication using OAuth PAR and OpenID Connect – Damien Bowden […]

Leave a reply to The Morning Brew - Chris Alcock » The Morning Brew #3828 Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.