From ddddd7d3ad9d87483331ff93b24020b619dc2b38 Mon Sep 17 00:00:00 2001 From: kbrekke Date: Tue, 9 Apr 2024 16:23:37 +0200 Subject: [PATCH 1/3] Set correct ClientCredentialStyle for use with ClientAssertion --- src/OidcClient/AuthorizeClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OidcClient/AuthorizeClient.cs b/src/OidcClient/AuthorizeClient.cs index 098f0b1..538f4e4 100644 --- a/src/OidcClient/AuthorizeClient.cs +++ b/src/OidcClient/AuthorizeClient.cs @@ -140,7 +140,9 @@ private async Task PushAuthorizationRequestAsync(st ClientSecret = _options.ClientSecret, ClientAssertion = await _options.GetClientAssertionAsync(), - + + ClientCredentialStyle = ClientCredentialStyle.PostBody, + Parameters = CreateAuthorizeParameters(state, codeChallenge, frontChannelParameters), }; From dced1ba4c241830c787cb931838a07d4c8d84f66 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Tue, 9 Apr 2024 13:48:12 -0500 Subject: [PATCH 2/3] Set client credential style to PostBody only if client assertion is set Client secrets are not recommended in the post body by RFC 6749. We should use post body (at least by default) only for client assertions. Also added tests of this behavior. --- src/OidcClient/AuthorizeClient.cs | 6 +- .../OidcClient.Tests/CodeFlowResponseTests.cs | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/OidcClient/AuthorizeClient.cs b/src/OidcClient/AuthorizeClient.cs index 538f4e4..fc69e06 100644 --- a/src/OidcClient/AuthorizeClient.cs +++ b/src/OidcClient/AuthorizeClient.cs @@ -141,11 +141,15 @@ private async Task PushAuthorizationRequestAsync(st ClientSecret = _options.ClientSecret, ClientAssertion = await _options.GetClientAssertionAsync(), - ClientCredentialStyle = ClientCredentialStyle.PostBody, Parameters = CreateAuthorizeParameters(state, codeChallenge, frontChannelParameters), }; + if(par.ClientAssertion?.Value != null) + { + par.ClientCredentialStyle = ClientCredentialStyle.PostBody; + } + return await http.PushAuthorizationAsync(par); } diff --git a/test/OidcClient.Tests/CodeFlowResponseTests.cs b/test/OidcClient.Tests/CodeFlowResponseTests.cs index 96fe810..12eaabf 100644 --- a/test/OidcClient.Tests/CodeFlowResponseTests.cs +++ b/test/OidcClient.Tests/CodeFlowResponseTests.cs @@ -16,6 +16,7 @@ using System.Threading.Tasks; using IdentityModel.Client; using Xunit; +using System.Web; namespace IdentityModel.OidcClient.Tests { @@ -486,5 +487,82 @@ public async Task Malformed_identity_token_on_token_response_should_fail() result.IsError.Should().BeTrue(); result.Error.Should().Contain("invalid_jwt"); } + + [Fact] + public async Task Authorize_should_push_parameters_when_PAR_is_enabled() + { + // Configure the client for PAR, authenticating with a client secret + _options.ClientSecret = "secret"; + _options.ProviderInformation.PushedAuthorizationRequestEndpoint = "https://this-is-set-so-par-will-be-used"; + var client = new OidcClient(_options); + + // Mock the response from the par endpoint + var requestUri = "mocked_request_uri"; + var parResponse = new Dictionary + { + { "request_uri", requestUri } + }; + var backChannelHandler = new NetworkHandler(JsonSerializer.Serialize(parResponse), HttpStatusCode.OK); + _options.BackchannelHandler = backChannelHandler; + + // Prepare the login to cause the backchannel PAR request + var state = await client.PrepareLoginAsync(); + + // Validate that the resulting PAR state is correct + var startUrl = new Uri(state.StartUrl); + var startUrlQueryParams = HttpUtility.ParseQueryString(startUrl.Query); + startUrlQueryParams.Should().HaveCount(2); + startUrlQueryParams.GetValues("client_id").Single().Should().Be("client"); + startUrlQueryParams.GetValues("request_uri").Single().Should().Be(requestUri); + + // Validate that the client authentication during the PAR request was correct + var request = backChannelHandler.Request; + request.Headers.Authorization.Should().NotBeNull(); + request.Headers.Authorization.Scheme.Should().Be("Basic"); + request.Headers.Authorization.Parameter.Should() + .Be(BasicAuthenticationOAuthHeaderValue.EncodeCredential("client", "secret")); + } + + [Fact] + public async Task Par_request_should_include_client_assertion_in_body() + { + // Configure the client for PAR, authenticating with a client assertion + var clientAssertion = "mocked_client_assertion"; + var clientAssertionType = "mocked_assertion_type"; + _options.ClientAssertion = new ClientAssertion + { + Type = clientAssertionType, + Value = clientAssertion + }; + _options.ProviderInformation.PushedAuthorizationRequestEndpoint = "https://this-is-set-so-par-will-be-used"; + var client = new OidcClient(_options); + + // Mock the response from the par endpoint + var requestUri = "mocked_request_uri"; + var parResponse = new Dictionary + { + { "request_uri", requestUri } + }; + var backChannelHandler = new NetworkHandler(JsonSerializer.Serialize(parResponse), HttpStatusCode.OK); + _options.BackchannelHandler = backChannelHandler; + + // Prepare the login to cause the backchannel PAR request + var state = await client.PrepareLoginAsync(); + + // Validate that the resulting PAR state is correct + var startUrl = new Uri(state.StartUrl); + var startUrlQueryParams = HttpUtility.ParseQueryString(startUrl.Query); + startUrlQueryParams.Should().HaveCount(2); + startUrlQueryParams.GetValues("client_id").Single().Should().Be("client"); + startUrlQueryParams.GetValues("request_uri").Single().Should().Be(requestUri); + + // Validate that the client authentication during the PAR request was correct + var parRequest = backChannelHandler.Request; + var parContent = await parRequest.Content.ReadAsStringAsync(); + var parParams = HttpUtility.ParseQueryString(parContent); + parParams.GetValues("client_assertion").Single().Should().Be(clientAssertion); + parParams.GetValues("client_assertion_type").Single().Should().Be(clientAssertionType); + parRequest.Headers.Authorization.Should().BeNull(); + } } } \ No newline at end of file From f611ad7db6f53b6acf86f3e24f1ae51bf5546e70 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Tue, 9 Apr 2024 13:49:00 -0500 Subject: [PATCH 3/3] Whitespace --- src/OidcClient/AuthorizeClient.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OidcClient/AuthorizeClient.cs b/src/OidcClient/AuthorizeClient.cs index fc69e06..6fed4e4 100644 --- a/src/OidcClient/AuthorizeClient.cs +++ b/src/OidcClient/AuthorizeClient.cs @@ -140,8 +140,6 @@ private async Task PushAuthorizationRequestAsync(st ClientSecret = _options.ClientSecret, ClientAssertion = await _options.GetClientAssertionAsync(), - - Parameters = CreateAuthorizeParameters(state, codeChallenge, frontChannelParameters), };