Skip to content

Commit

Permalink
Set client credential style to PostBody only if client assertion is set
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
josephdecock committed Apr 9, 2024
1 parent ddddd7d commit dced1ba
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/OidcClient/AuthorizeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,15 @@ private async Task<PushedAuthorizationResponse> 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);
}

Expand Down
78 changes: 78 additions & 0 deletions test/OidcClient.Tests/CodeFlowResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Threading.Tasks;
using IdentityModel.Client;
using Xunit;
using System.Web;

namespace IdentityModel.OidcClient.Tests
{
Expand Down Expand Up @@ -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<string, string>
{
{ "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<string, string>
{
{ "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();
}
}
}

0 comments on commit dced1ba

Please sign in to comment.