From 101622e8e2264038c67342ae6e3e17286caae4a7 Mon Sep 17 00:00:00 2001 From: Charley Wu Date: Sat, 2 Apr 2022 21:49:49 +0800 Subject: [PATCH 1/4] Bump dotnet-reportgenerator-globaltool from 5.0.4 to 5.1.3 --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 751ca51..9b5426e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.0.4", + "version": "5.1.3", "commands": [ "reportgenerator" ] From b5b1961fa45a26541c6acf76fa6d37e9cf972e9c Mon Sep 17 00:00:00 2001 From: Charley Wu Date: Sat, 2 Apr 2022 21:36:32 +0800 Subject: [PATCH 2/4] Prefer to send the client credentials in Authorization header - https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 --- samples/OAuth2HttpClientSample/Program.cs | 2 + .../AuthorizerDefaults.cs | 1 + .../AuthorizerOptions.cs | 7 ++++ .../Authorizers/AccessTokenAuthorizerBase.cs | 18 +++++--- .../OAuth2Fixture.cs | 1 + .../ClientCredentialsAuthorizerTests.cs | 41 ++++++++++--------- ...ResourceOwnerCredentialsAuthorizerTests.cs | 18 ++++---- 7 files changed, 54 insertions(+), 34 deletions(-) diff --git a/samples/OAuth2HttpClientSample/Program.cs b/samples/OAuth2HttpClientSample/Program.cs index 2f1828c..51e2721 100644 --- a/samples/OAuth2HttpClientSample/Program.cs +++ b/samples/OAuth2HttpClientSample/Program.cs @@ -11,6 +11,8 @@ static void ConfigureAuthorizerOptions(IServiceProvider resolver, AuthorizerOpti options.AccessTokenEndpoint = configuration.GetValue("OAuth2:AccessTokenEndpoint"); options.ClientId = configuration["OAuth2:ClientId"]; options.ClientSecret = configuration["OAuth2:ClientSecret"]; + options.SendClientCredentialsInRequestBody = + configuration.GetValue("OAuth2:SendClientCredentialsInRequestBody", false); options.Credentials = new NetworkCredential( configuration["OAuth2:Credentials:UserName"], configuration["OAuth2:Credentials:Password"]); diff --git a/src/GSS.Authorization.OAuth2/AuthorizerDefaults.cs b/src/GSS.Authorization.OAuth2/AuthorizerDefaults.cs index 28d8a02..c1e4760 100644 --- a/src/GSS.Authorization.OAuth2/AuthorizerDefaults.cs +++ b/src/GSS.Authorization.OAuth2/AuthorizerDefaults.cs @@ -2,6 +2,7 @@ namespace GSS.Authorization.OAuth2 { public static class AuthorizerDefaults { + public const string Basic = "Basic"; public const string Bearer = "Bearer"; public const string ClientId = "client_id"; diff --git a/src/GSS.Authorization.OAuth2/AuthorizerOptions.cs b/src/GSS.Authorization.OAuth2/AuthorizerOptions.cs index fa3402d..85600bc 100644 --- a/src/GSS.Authorization.OAuth2/AuthorizerOptions.cs +++ b/src/GSS.Authorization.OAuth2/AuthorizerOptions.cs @@ -15,6 +15,13 @@ public class AuthorizerOptions [Required] public string ClientSecret { get; set; } = default!; + + /* + * send the client credentials in the request-body? (default: Authorization header) + * + * see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 + */ + public bool SendClientCredentialsInRequestBody { get; set; } public IEnumerable? Scopes { get; set; } diff --git a/src/GSS.Authorization.OAuth2/Authorizers/AccessTokenAuthorizerBase.cs b/src/GSS.Authorization.OAuth2/Authorizers/AccessTokenAuthorizerBase.cs index 516a6e8..17bba5e 100644 --- a/src/GSS.Authorization.OAuth2/Authorizers/AccessTokenAuthorizerBase.cs +++ b/src/GSS.Authorization.OAuth2/Authorizers/AccessTokenAuthorizerBase.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -35,21 +37,27 @@ protected AccessTokenAuthorizerBase(HttpClient client, IOptions GetAccessTokenAsync(CancellationToken cancellationToken = default) { - var formData = new Dictionary - { - [AuthorizerDefaults.ClientId] = Options.ClientId, - [AuthorizerDefaults.ClientSecret] = Options.ClientSecret - }; + var formData = new Dictionary(); if (Options.Scopes != null) { formData.Add(AuthorizerDefaults.Scope, string.Join(AuthorizerDefaults.ScopeSeparator, Options.Scopes)); } + if (Options.SendClientCredentialsInRequestBody) + { + formData.Add(AuthorizerDefaults.ClientId, Options.ClientId); + formData.Add(AuthorizerDefaults.ClientSecret, Options.ClientSecret); + } PrepareFormData(formData); using var request = new HttpRequestMessage(HttpMethod.Post, Options.AccessTokenEndpoint) { Content = new FormUrlEncodedContent(formData) }; + if (!Options.SendClientCredentialsInRequestBody) + { + request.Headers.Authorization = new AuthenticationHeaderValue(AuthorizerDefaults.Basic, + Convert.ToBase64String(Encoding.ASCII.GetBytes($"{Options.ClientId}:{Options.ClientSecret}"))); + } var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); if (response.IsSuccessStatusCode) diff --git a/test/GSS.Authorization.OAuth2.HttpClient.Tests/OAuth2Fixture.cs b/test/GSS.Authorization.OAuth2.HttpClient.Tests/OAuth2Fixture.cs index bda9179..ac92d11 100644 --- a/test/GSS.Authorization.OAuth2.HttpClient.Tests/OAuth2Fixture.cs +++ b/test/GSS.Authorization.OAuth2.HttpClient.Tests/OAuth2Fixture.cs @@ -40,6 +40,7 @@ private void ConfigureAuthroizerOptions(IServiceProvider resolver, AuthorizerOpt options.AccessTokenEndpoint = Configuration.GetValue("OAuth2:AccessTokenEndpoint"); options.ClientId = Configuration["OAuth2:ClientId"]; options.ClientSecret = Configuration["OAuth2:ClientSecret"]; + options.SendClientCredentialsInRequestBody = true; options.Credentials = new NetworkCredential( Configuration["OAuth2:Credentials:UserName"], Configuration["OAuth2:Credentials:Password"]); diff --git a/test/GSS.Authorization.OAuth2.Tests/ClientCredentialsAuthorizerTests.cs b/test/GSS.Authorization.OAuth2.Tests/ClientCredentialsAuthorizerTests.cs index bb58f18..cbeb30b 100644 --- a/test/GSS.Authorization.OAuth2.Tests/ClientCredentialsAuthorizerTests.cs +++ b/test/GSS.Authorization.OAuth2.Tests/ClientCredentialsAuthorizerTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text; using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -15,6 +16,7 @@ public class ClientCredentialsAuthorizerTests : IClassFixture private readonly AuthorizerOptions _options; private HttpStatusCode _errorStatusCode; private string? _errorMessage; + private readonly string _basicAuthHeaderValue; public ClientCredentialsAuthorizerTests(AuthorizerFixture fixture) { @@ -22,6 +24,7 @@ public ClientCredentialsAuthorizerTests(AuthorizerFixture fixture) { _mockHttp = new MockHttpMessageHandler(); } + var services = fixture.BuildAuthorizer(_mockHttp, (code, s) => { _errorStatusCode = code; @@ -29,6 +32,8 @@ public ClientCredentialsAuthorizerTests(AuthorizerFixture fixture) }); _authorizer = services.GetRequiredService(); _options = services.GetRequiredService>().Value; + _basicAuthHeaderValue = + $"Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_options.ClientId}:{_options.ClientSecret}"))}"; } [Fact] @@ -36,14 +41,13 @@ public async Task Authorizer_GetAccessToken_ShouldNotNull() { // Arrange _mockHttp?.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.ClientCredentials) - .Respond("application/json", JsonSerializer.Serialize(new AccessToken - { - Token = Guid.NewGuid().ToString(), - ExpiresInSeconds = 10 - })); + .Respond("application/json", + JsonSerializer.Serialize(new AccessToken + { + Token = Guid.NewGuid().ToString(), ExpiresInSeconds = 10 + })); // Act var accessToken = await _authorizer.GetAccessTokenAsync().ConfigureAwait(false); @@ -58,14 +62,13 @@ public async Task Authorizer_GetAccessToken_ShouldNotEmpty() { // Arrange _mockHttp?.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.ClientCredentials) - .Respond("application/json", JsonSerializer.Serialize(new AccessToken - { - Token = Guid.NewGuid().ToString(), - ExpiresInSeconds = 10 - })); + .Respond("application/json", + JsonSerializer.Serialize(new AccessToken + { + Token = Guid.NewGuid().ToString(), ExpiresInSeconds = 10 + })); // Act var accessToken = await _authorizer.GetAccessTokenAsync().ConfigureAwait(false); @@ -82,8 +85,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldReturnNull() // Arrange _mockHttp.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.ClientCredentials) .Respond(HttpStatusCode.InternalServerError); @@ -91,7 +93,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldReturnNull() var accessToken = await _authorizer.GetAccessTokenAsync().ConfigureAwait(false); // Assert - Assert.Null(accessToken?.Token); + Assert.Null(accessToken.Token); _mockHttp.VerifyNoOutstandingExpectation(); } @@ -103,8 +105,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldInvokeErrorHandle // Arrange var expectedErrorMessage = Guid.NewGuid().ToString(); _mockHttp.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.ClientCredentials) .Respond(HttpStatusCode.InternalServerError, "application/json", expectedErrorMessage); @@ -117,4 +118,4 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldInvokeErrorHandle _mockHttp.VerifyNoOutstandingExpectation(); } } -} +} \ No newline at end of file diff --git a/test/GSS.Authorization.OAuth2.Tests/ResourceOwnerCredentialsAuthorizerTests.cs b/test/GSS.Authorization.OAuth2.Tests/ResourceOwnerCredentialsAuthorizerTests.cs index 4413832..5503f65 100644 --- a/test/GSS.Authorization.OAuth2.Tests/ResourceOwnerCredentialsAuthorizerTests.cs +++ b/test/GSS.Authorization.OAuth2.Tests/ResourceOwnerCredentialsAuthorizerTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text; using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -15,6 +16,7 @@ public class ResourceOwnerCredentialsAuthorizerTests : IClassFixture(); _options = services.GetRequiredService>().Value; + _basicAuthHeaderValue = + $"Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_options.ClientId}:{_options.ClientSecret}"))}"; } [Fact] @@ -36,8 +40,7 @@ public async Task Authorizer_GetAccessToken_ShouldNotNull() { // Arrange _mockHttp?.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.Password) .WithFormData(AuthorizerDefaults.Username, _options.Credentials?.UserName) .WithFormData(AuthorizerDefaults.Password, _options.Credentials?.Password) @@ -60,8 +63,7 @@ public async Task Authorizer_GetAccessToken_ShouldNotEmpty() { // Arrange _mockHttp?.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.Password) .WithFormData(AuthorizerDefaults.Username, _options.Credentials?.UserName) .WithFormData(AuthorizerDefaults.Password, _options.Credentials?.Password) @@ -86,8 +88,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldReturnNull() // Arrange _mockHttp.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.Password) .WithFormData(AuthorizerDefaults.Username, _options.Credentials?.UserName) .WithFormData(AuthorizerDefaults.Password, _options.Credentials?.Password) @@ -97,7 +98,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldReturnNull() var accessToken = await _authorizer.GetAccessTokenAsync().ConfigureAwait(false); // Assert - Assert.Null(accessToken?.Token); + Assert.Null(accessToken.Token); _mockHttp.VerifyNoOutstandingExpectation(); } @@ -109,8 +110,7 @@ public async Task Authorizer_GetAccessTokenWithException_ShouldInvokeErrorHandle // Arrange var expectedErrorMessage = Guid.NewGuid().ToString(); _mockHttp.Expect(HttpMethod.Post, _options.AccessTokenEndpoint.AbsoluteUri) - .WithFormData(AuthorizerDefaults.ClientId, _options.ClientId) - .WithFormData(AuthorizerDefaults.ClientSecret, _options.ClientSecret) + .WithHeaders("Authorization", _basicAuthHeaderValue) .WithFormData(AuthorizerDefaults.GrantType, AuthorizerDefaults.Password) .WithFormData(AuthorizerDefaults.Username, _options.Credentials?.UserName) .WithFormData(AuthorizerDefaults.Password, _options.Credentials?.Password) From 421c3793f96595ce2c67212fe82df230dd70485d Mon Sep 17 00:00:00 2001 From: Charley Wu Date: Sat, 2 Apr 2022 22:29:23 +0800 Subject: [PATCH 3/4] Added UserAgent header for identification --- samples/OAuth2HttpClientSample/Program.cs | 35 +++++++++++-------- samples/OAuthHttpClientSample/Program.cs | 34 +++++++++++------- .../OAuthHttpClientSample/appsettings.json | 1 + .../Program.cs | 7 +++- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/samples/OAuth2HttpClientSample/Program.cs b/samples/OAuth2HttpClientSample/Program.cs index 51e2721..8bba8ce 100644 --- a/samples/OAuth2HttpClientSample/Program.cs +++ b/samples/OAuth2HttpClientSample/Program.cs @@ -20,28 +20,33 @@ static void ConfigureAuthorizerOptions(IServiceProvider resolver, AuthorizerOpti options.OnError = (code, message) => Console.Error.Write($"ERROR: [${code}]: {message}"); } -var host = Host.CreateDefaultBuilder(args) -.ConfigureServices((hostContext, services) => +static void ConfigureHttpClient(HttpClient client) { - var clientBuilder = - hostContext.Configuration.GetValue("OAuth2:GrantFlow", "ClientCredentials") - .Equals("ClientCredentials", StringComparison.OrdinalIgnoreCase) - ? services.AddOAuth2HttpClient( - ConfigureAuthorizerOptions) - : services.AddOAuth2HttpClient( - ConfigureAuthorizerOptions); - clientBuilder.ConfigureHttpClient(client => client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"))); -}).Build(); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("OAuth2HttpClientSample", "1.0")); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); +} + +var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + var clientBuilder = + hostContext.Configuration.GetValue("OAuth2:GrantFlow", "ClientCredentials") + .Equals("ClientCredentials", StringComparison.OrdinalIgnoreCase) + ? services.AddOAuth2HttpClient( + ConfigureAuthorizerOptions, builder => builder.ConfigureHttpClient(ConfigureHttpClient)) + : services.AddOAuth2HttpClient( + ConfigureAuthorizerOptions, builder => builder.ConfigureHttpClient(ConfigureHttpClient)); + clientBuilder.ConfigureHttpClient(ConfigureHttpClient); + }).Build(); var configuration = host.Services.GetRequiredService(); Console.WriteLine("Creating a client..."); var oauth2Client = host.Services.GetRequiredService(); Console.WriteLine("Sending a request..."); -var response = await oauth2Client.HttpClient.GetAsync(configuration.GetValue("OAuth2:ResourceEndpoint")).ConfigureAwait(false); +var response = await oauth2Client.HttpClient.GetAsync(configuration.GetValue("OAuth2:ResourceEndpoint")) + .ConfigureAwait(false); Console.WriteLine("Response data:"); var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false); -Console.WriteLine(data); - - +Console.WriteLine(data); \ No newline at end of file diff --git a/samples/OAuthHttpClientSample/Program.cs b/samples/OAuthHttpClientSample/Program.cs index 57165f4..1c88fbb 100644 --- a/samples/OAuthHttpClientSample/Program.cs +++ b/samples/OAuthHttpClientSample/Program.cs @@ -5,20 +5,23 @@ using Microsoft.Extensions.Hosting; var host = Host.CreateDefaultBuilder(args) -.ConfigureServices((hostContext, services) => -{ - services.AddOAuthHttpClient((_, options) => + .ConfigureServices((hostContext, services) => { - options.ClientCredentials = new OAuthCredential( - hostContext.Configuration["OAuth:ClientId"], - hostContext.Configuration["OAuth:ClientSecret"]); - options.TokenCredentials = new OAuthCredential( + services.AddOAuthHttpClient((_, options) => + { + options.ClientCredentials = new OAuthCredential( + hostContext.Configuration["OAuth:ClientId"], + hostContext.Configuration["OAuth:ClientSecret"]); + options.TokenCredentials = new OAuthCredential( hostContext.Configuration["OAuth:TokenId"], hostContext.Configuration["OAuth:TokenSecret"]); - options.SignedAsQuery = hostContext.Configuration.GetValue("OAuth:SignedAsQuery", false); - options.SignedAsBody = hostContext.Configuration.GetValue("OAuth:SignedAsBody", false); - }).ConfigureHttpClient(client => client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"))); -}).Build(); + options.SignedAsQuery = hostContext.Configuration.GetValue("OAuth:SignedAsQuery", false); + options.SignedAsBody = hostContext.Configuration.GetValue("OAuth:SignedAsBody", false); + }).ConfigureHttpClient(client => + { + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("OAuthHttpClientSample", "1.0")); + }); + }).Build(); var configuration = host.Services.GetRequiredService(); @@ -28,13 +31,20 @@ Console.WriteLine("Sending a request..."); var method = new HttpMethod(configuration.GetValue("Request:Method", HttpMethod.Get.Method)); var request = new HttpRequestMessage(method, configuration.GetValue("Request:Uri")); +var accept = configuration["Request:Accept"]; +if (!string.IsNullOrWhiteSpace(accept)) +{ + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(accept)); +} + var body = configuration.GetSection("Request:Body").Get>(); if (body != null) { request.Content = new FormUrlEncodedContent(body); } + var response = await oauthClient.HttpClient.SendAsync(request).ConfigureAwait(false); Console.WriteLine("Response data:"); var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false); -Console.WriteLine(data); +Console.WriteLine(data); \ No newline at end of file diff --git a/samples/OAuthHttpClientSample/appsettings.json b/samples/OAuthHttpClientSample/appsettings.json index f80a2f1..1a6999e 100644 --- a/samples/OAuthHttpClientSample/appsettings.json +++ b/samples/OAuthHttpClientSample/appsettings.json @@ -12,6 +12,7 @@ }, "Request": { "Method": "GET", + "Accept": "application/json", "Uri": "https://example.com/oauth/profile" } } \ No newline at end of file diff --git a/samples/OAuthInteractiveConsoleAuthorizer/Program.cs b/samples/OAuthInteractiveConsoleAuthorizer/Program.cs index 20ffffa..62f590e 100644 --- a/samples/OAuthInteractiveConsoleAuthorizer/Program.cs +++ b/samples/OAuthInteractiveConsoleAuthorizer/Program.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Net.Http.Headers; using GSS.Authorization.OAuth; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -21,7 +22,11 @@ .PostConfigure(options => Validator.ValidateObject(options, new ValidationContext(options), true)); services.AddSingleton(); services.AddHttpClient() - .ConfigureHttpClient(client => client.BaseAddress = context.Configuration.GetValue("OAuth:BaseAddress")); + .ConfigureHttpClient(client => + { + client.BaseAddress = context.Configuration.GetValue("OAuth:BaseAddress"); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("OAuthInteractiveConsoleAuthorizer", "1.0")); + }); services.AddTransient(resolver => resolver.GetRequiredService()); }).Build(); From 3662777517db13c65d754568fadfa65a84737252 Mon Sep 17 00:00:00 2001 From: Charley Wu Date: Sat, 2 Apr 2022 21:56:42 +0800 Subject: [PATCH 4/4] Bump version to 2.4.0 --- CHANGELOG.md | 4 ++++ src/Directory.Build.props | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebe1191..ddbb977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 2.4.0 (2022-04-02) + +- [Prefer to send the client credentials in Authorization header](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1) + ## 2.3.1 (2021-11-14) - Floating latest dependencies for .NET 6 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 13218b2..374b6cd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -10,7 +10,7 @@ true true enable - 2.3.1 + 2.4.0