From d0c313b0d3d73640184ac64851762503316929bf Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 24 Jul 2024 22:11:07 +0800 Subject: [PATCH] Update auth implementation to match .NET 8 (#165) * Update security stub implementation * Move to .NET 8 only --- src/Alba.Testing/Alba.Testing.csproj | 12 +++++----- .../Samples/ContractTestWithAlba.cs | 2 +- .../Security/JwtSecurityStubTests.cs | 24 +++++++++---------- .../OpenConnectClientCredentialsTests.cs | 2 +- src/Alba/Alba.csproj | 19 ++++----------- src/Alba/Security/JwtSecurityStub.cs | 19 ++++++++++----- src/Directory.Build.props | 2 +- .../IdentityServer.New.csproj | 8 +++---- ...-key-BAF757129D4DA200C5FD1BF53156790E.json | 1 + .../MinimalApiWithOakton.csproj | 2 +- src/NUnitSamples/NUnitSamples.csproj | 4 ++-- .../WebApiStartupHostingModel.csproj | 2 +- src/WebApiNet6/WebApiNet6.csproj | 2 +- src/WebApp/Controllers/FakeController.cs | 13 ++++------ src/WebApp/Startup.cs | 7 +++--- src/WebApp/WebApp.csproj | 7 +++--- src/WebAppSecuredWithJwt/Startup.cs | 5 ++-- .../WebAppSecuredWithJwt.csproj | 7 +++--- 18 files changed, 62 insertions(+), 76 deletions(-) create mode 100644 src/IdentityServer.New/keys/is-signing-key-BAF757129D4DA200C5FD1BF53156790E.json diff --git a/src/Alba.Testing/Alba.Testing.csproj b/src/Alba.Testing/Alba.Testing.csproj index 2aa8eeff..f4a0d064 100644 --- a/src/Alba.Testing/Alba.Testing.csproj +++ b/src/Alba.Testing/Alba.Testing.csproj @@ -1,7 +1,7 @@ - + - net6.0;net8.0 - portable + net8.0 + false @@ -14,11 +14,11 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Alba.Testing/Samples/ContractTestWithAlba.cs b/src/Alba.Testing/Samples/ContractTestWithAlba.cs index 443e6133..3a5c97e8 100644 --- a/src/Alba.Testing/Samples/ContractTestWithAlba.cs +++ b/src/Alba.Testing/Samples/ContractTestWithAlba.cs @@ -65,7 +65,7 @@ public async Task with_validation_errors() { _.Get.Url("/fake/invalid"); _.ContentTypeShouldBe("application/problem+json; charset=utf-8"); - _.StatusCodeShouldBe(400); + _.StatusCodeShouldBe(500); }); var problems = result.ReadAsJson(); diff --git a/src/Alba.Testing/Security/JwtSecurityStubTests.cs b/src/Alba.Testing/Security/JwtSecurityStubTests.cs index 3cdd163c..f4385475 100644 --- a/src/Alba.Testing/Security/JwtSecurityStubTests.cs +++ b/src/Alba.Testing/Security/JwtSecurityStubTests.cs @@ -39,23 +39,20 @@ public JwtSecurityStubTests() public void token_has_identifier() { theStub.BuildToken() - .Claims.ShouldContain(x => x.Type == JwtRegisteredClaimNames.Jti); + .Subject.Claims.ShouldContain(x => x.Type == JwtRegisteredClaimNames.Jti); } [Fact] public void should_have_audience() { var token = theStub.BuildToken(); - token - .Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud) - .Value.ShouldBe("chiefsfans"); + token.Audience.ShouldBe("chiefsfans"); } [Fact] public void should_have_issuer_if_it_exists() { - theStub.BuildToken() - .Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Iss) + theStub.BuildToken().Subject.Claims.First(x => x.Type == JwtRegisteredClaimNames.Iss) .Value.ShouldBe("myapp"); } @@ -63,11 +60,13 @@ public void should_have_issuer_if_it_exists() public void has_all_the_baseline_claims() { var token = theStub.BuildToken(); - - token.Claims.Single(x => x.Type == "foo") + + token + .Subject.Claims.Single(x => x.Type == "foo") .Value.ShouldBe("bar"); - - token.Claims.Single(x => x.Type == "team") + + token + .Subject.Claims.Single(x => x.Type == "team") .Value.ShouldBe("chiefs"); } @@ -75,8 +74,9 @@ public void has_all_the_baseline_claims() public void additive_claims_on_the_token() { var token = theStub.BuildToken(new []{new Claim("division", "afcwest")}); - - token.Claims.Single(x => x.Type == "division") + + token + .Subject.Claims.Single(x => x.Type == "division") .Value.ShouldBe("afcwest"); } diff --git a/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs b/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs index 5ec01f14..c81d7101 100644 --- a/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs +++ b/src/Alba.Testing/Security/OpenConnectClientCredentialsTests.cs @@ -134,7 +134,7 @@ public async Task post_to_a_secured_endpoint_with_jwt_from_extension() x.StatusCodeShouldBeOk(); }); - var output = response.ReadAsJson(); + var output = await response.ReadAsJsonAsync(); output.Sum.ShouldBe(9); output.Product.ShouldBe(24); } diff --git a/src/Alba/Alba.csproj b/src/Alba/Alba.csproj index 54ae92d5..993c24ef 100644 --- a/src/Alba/Alba.csproj +++ b/src/Alba/Alba.csproj @@ -3,14 +3,13 @@ Supercharged integration testing for ASP.NET Core HTTP endpoints 8.0.0 - net6.0;net7.0;net8.0 + net8.0 http://jasperfx.github.io/alba/ Apache-2.0 git git://github.com/JasperFx/alba library https://avatars2.githubusercontent.com/u/10048186?v=3&s=200 - 10 enable true CS1591; @@ -19,22 +18,12 @@ - - - - - - - - - - - + - - + + diff --git a/src/Alba/Security/JwtSecurityStub.cs b/src/Alba/Security/JwtSecurityStub.cs index ff646904..ce1ce763 100644 --- a/src/Alba/Security/JwtSecurityStub.cs +++ b/src/Alba/Security/JwtSecurityStub.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; @@ -10,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; @@ -61,7 +61,7 @@ IHostBuilder IAlbaExtension.Configure(IHostBuilder builder) }); } - internal JwtSecurityToken BuildToken(Claim[]? additionalClaims = null, string[]? removedClaims = null) + internal SecurityTokenDescriptor BuildToken(Claim[]? additionalClaims = null, string[]? removedClaims = null) { if (_options == null) throw new InvalidOperationException("Unable to determine the JwtBearerOptions for this AlbaHost"); @@ -77,14 +77,21 @@ internal JwtSecurityToken BuildToken(Claim[]? additionalClaims = null, string[]? _options.TokenValidationParameters.IssuerSigningKey, algorithm); - return new JwtSecurityToken(_options.ClaimsIssuer, audience, processedClaims(additionalClaims, removedClaims), - expires: DateTime.UtcNow.AddDays(1), signingCredentials: credentials); + return new SecurityTokenDescriptor() + { + Subject = new ClaimsIdentity(processedClaims(additionalClaims, removedClaims)), + Issuer = _options.ClaimsIssuer, + Audience = audience, + Expires = DateTime.UtcNow.AddDays(1), + SigningCredentials = credentials + }; } internal string BuildJwtString(Claim[] additionalClaims, string[]? removedClaims = null) { - var token = BuildToken(additionalClaims, removedClaims); - return new JwtSecurityTokenHandler().WriteToken(token); + var tokenHandler = new JsonWebTokenHandler(); + var tokenDescriptor = BuildToken(additionalClaims, removedClaims); + return tokenHandler.CreateToken(tokenDescriptor); } protected override IEnumerable stubTypeSpecificClaims() diff --git a/src/Directory.Build.props b/src/Directory.Build.props index cfdeba69..f391f142 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,7 @@ - NU1901;NU1902;NU1903;NU1904 + NU1901;NU1902;NU1904 all low diff --git a/src/IdentityServer.New/IdentityServer.New.csproj b/src/IdentityServer.New/IdentityServer.New.csproj index ab43d217..c2e0e3d5 100644 --- a/src/IdentityServer.New/IdentityServer.New.csproj +++ b/src/IdentityServer.New/IdentityServer.New.csproj @@ -1,16 +1,14 @@  - net6.0 + net8.0 enable - + - - - + diff --git a/src/IdentityServer.New/keys/is-signing-key-BAF757129D4DA200C5FD1BF53156790E.json b/src/IdentityServer.New/keys/is-signing-key-BAF757129D4DA200C5FD1BF53156790E.json new file mode 100644 index 00000000..25fa03f3 --- /dev/null +++ b/src/IdentityServer.New/keys/is-signing-key-BAF757129D4DA200C5FD1BF53156790E.json @@ -0,0 +1 @@ +{"Version":1,"Id":"BAF757129D4DA200C5FD1BF53156790E","Created":"2024-07-24T11:43:55.5580267Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8AjiBY8lL4pOrrMS71-cAqmVF6QemWkgsWqkQAIok3liy6J9PfVwWxEIomXKxStcXE88fpWoYhfB3-h4-kvyR1H-Oek6or63nKXWOdVZb2gaEGUlkfIrJLYkksVCa_Wg21MqCrK9d4aagyCyCur-UnoGREKEq1QRBf7OZ-uQyrAP3bzj4-S8AUWMCmgw848ObGW2TRcUUJuBVABCKkGjYb-NAWS8_LXVsXcCgqkGe-VcKZzUYsJSCShA8eQ4cy1AXPkjkQX12pQDpahLW0I4i9o5uRqzhXdeGj3Jl1yUZQGVUfoXRj-oJzsI0OZfDvdMcaM10C19pFtUtNdIsHG3LnaexwD3006fSC9InPJjWqfI1C7JypPAsQ0aXumSlk40yXqe6XrGyb3eBiWrIxwW-SkP1fZIV505zecvQepMZIstjxulBCjpjXC4XMalv6i1J2T67DTOAKOAe1OwckKgVV9I4-bVshlT3zNj6aBTEoDydhLSelFnanJALw5MN_qtVfDvt1902TDVaaK3wrI9e9kWDMGSh1ctgFy3bP28MgJlZ1_BJj2i1_5yCvZ93txKhz55whUA7OuPZRH1SmtcOjzUGqKXhkRGElVCFP6djFKMcO1G7WhpyrFa8ge8NE-eJp_qpAdMK0TNp0a4D9hwGfr3xMsQ0sfMt26twbWNjwqmB0qBaxRoKy493FC93GxfNaxrG_1eqJDyBKkmOyLbILnhzUYuYlEf3Ea3_o4pewqLQaShiQaRbkQY8ZDHUAQ4Mu3Y6GKetSHzB2UTy7NWcSnpuV06w_5OhnGhWjnXiNF4LCnaEXiyvx3xdmrhMq0f69WK-FEhOBN4oRRdH4Z1p8OcELMeIRsGkNcptDAPe1XBCvSBr8g4FxLO-ybxwmYlftmNQnA4pAFUDyCQd5l3VllKGhEerRdT4h3HXawnIxEr9R6wrsGDaWeRbFc7Mh0mH9XWulOFarv-TwoELuKMUfq8IaNwsgYgkJpR0kzRo6CykVIgXy6tKHz6ELK4harXtN4dGuNvtEtrmN19AXJzoESEbuCCLsT3nbwgE8kDaFrwdbgWhs0jjrDPjJfXyO4JSaLv1xikIzFRIOhHqXds8viu9vtshQ58ENeUerCF_ODugkzKM7vV7X0WPKlidZ4K6umv-Qh6jQ177ozvlPZBy2zOActfQmOqjXMxTzCBATajF1cupo_Upkfw2CMCsaA-FH1Ostxl27Ot_ZOvv2oP_YeA0leW1-AbqEVDbaIpjN6ri1fJDXPfZEMBj1UT8Q5u-qIdhFHh08iGK3Fh_PSFkrT8pSn3lin8xeEcWK40J9jgar-pFeCrq5ZYGc-xYHnodhnMKJ8WiSyrcJ0cDEPDgtKXDEKx-OUlGcJXhHDTTgopBARZHQxRNvuDFxfgk9fZJRLbQWg4H3CXLgO4kXjv5bzqy-NWwazIV0ZM-Hd1F3V8JQXB-EfO5JvPldZcOm2bNOtQ-jlqDgtC_1zRUV6NnBG3N3DcJz6_z9kliLD4U85yJCr8fYTcgC9EK65-ZVFxbL81J17e6AKYmsQfD7NAG9SoA9IycJ3Yz3vXTHVdsCl-2TrkhSfwaHN1bNMI3chWDHc2Q9A0j73_3SQ71h-BSQTiSFO-7dNEoqzN_6qhHE3TvEdSfCBkgp1GXotQo6Ms8X9vNvxpfH0uU67P3cxUxjtDYTv-Q9YjUclNZ9Qk2_ZOXkOkuCRQSg9t93tn7Ird9g3EnZ5yuqL6H_u19nNKmqDSqwVefeElyZL7WxnRrdgdsVvXP-OjCtzPUoQQAP9xiI3LnFXddzhEyJyiNEwjTxy1J2VAJCYnD6WDiVxbuYildyb5h5GjNHgTpLDL7TKPW6eg40psasSIsHQ52oiTnnQlLnRPTiXw4UYz1jISprKo0FOgNapKisnROuS_MJR7j2pr0asw7ZO5LHHqHj5hs_ITGM7rthIbEOZi16L37i6jNvTNU7JpTen-DPDmw066ZdZP60v3VZN0TghEWOPsBCGCCWHzC_wUUCUxprwLSkbhgcR-3W5Klc2TYWjnfCD0BtQ4kGu4FeDsgOaRMh7yAF2Q5dYa0fUg3rUBSq-0iEOfB1xvdd4gAiNfnlbZxekFxCH4De9dK572yOnBq9q06etTpxYEkPN_7pXSkPuVoXKpjpWTXPxwlyqIx8isBrdM__cAO6uRZxOXIXuv9nBE5pZ6aU1LysNtiSmYdLsHg9atAz8QYIJG1GON4BgeXI4vZTwdtsSeyAg3HozM0LwkJgjfA7uJtHrBP1GUWhimNftAV9V0_got5HlJhMHcY0ZveOk-l7dJGdYcP9Pah5L1YabbsaBjHKqt5w8TMw8YuprcWxTvWPSQeAX9jSy8pOeFdMrhTf7nhKxm5Wy09YuC20hRjxkMnVuveYUH8fsIDqW2ECbbiG2pH-mAoa_uNZz1bHRajJxwyLkUh7pO3bbg3p44UvGKSG95N1b3jR4NwxHq","DataProtected":true} \ No newline at end of file diff --git a/src/MinimalApiWithOakton/MinimalApiWithOakton.csproj b/src/MinimalApiWithOakton/MinimalApiWithOakton.csproj index f644da1b..e461bec1 100644 --- a/src/MinimalApiWithOakton/MinimalApiWithOakton.csproj +++ b/src/MinimalApiWithOakton/MinimalApiWithOakton.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NUnitSamples/NUnitSamples.csproj b/src/NUnitSamples/NUnitSamples.csproj index 472124b6..f5b5a925 100644 --- a/src/NUnitSamples/NUnitSamples.csproj +++ b/src/NUnitSamples/NUnitSamples.csproj @@ -1,14 +1,14 @@ - net6.0;net7.0 + net8.0 false - + diff --git a/src/WebApiAspNetCore3/WebApiStartupHostingModel.csproj b/src/WebApiAspNetCore3/WebApiStartupHostingModel.csproj index bc5bd0f7..5ac18973 100644 --- a/src/WebApiAspNetCore3/WebApiStartupHostingModel.csproj +++ b/src/WebApiAspNetCore3/WebApiStartupHostingModel.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 diff --git a/src/WebApiNet6/WebApiNet6.csproj b/src/WebApiNet6/WebApiNet6.csproj index 493a6f7f..3dcb4845 100644 --- a/src/WebApiNet6/WebApiNet6.csproj +++ b/src/WebApiNet6/WebApiNet6.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0 + net8.0 enable enable diff --git a/src/WebApp/Controllers/FakeController.cs b/src/WebApp/Controllers/FakeController.cs index 9a1576f0..6484ae52 100644 --- a/src/WebApp/Controllers/FakeController.cs +++ b/src/WebApp/Controllers/FakeController.cs @@ -1,5 +1,5 @@ using System; -using Hellang.Middleware.ProblemDetails; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace WebApp.Controllers @@ -9,7 +9,7 @@ namespace WebApp.Controllers public class FakeController : ControllerBase { [HttpGet("{status}")] - public string Fake(string status) + public IResult Fake(string status) { switch (status) { @@ -17,16 +17,11 @@ public string Fake(string status) throw new DivideByZeroException("Boom!"); case "invalid": - throw new ProblemDetailsException(new ProblemDetails - { - Status = 400, - Detail = "It's all wrong", - Title = "This stinks!" - }); + return Results.Problem("It's all wrong", title: "This stinks!"); default: - return "it's all good"; + return Results.Ok("it's all good"); } } } diff --git a/src/WebApp/Startup.cs b/src/WebApp/Startup.cs index 769e196a..3a66f910 100644 --- a/src/WebApp/Startup.cs +++ b/src/WebApp/Startup.cs @@ -1,5 +1,4 @@ -using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -35,8 +34,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) FileProvider = new PhysicalFileProvider(env.ContentRootPath) }); - app.UseProblemDetails(); - + app.UseExceptionHandler(); + app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); diff --git a/src/WebApp/WebApp.csproj b/src/WebApp/WebApp.csproj index 8c13c863..6094cadf 100644 --- a/src/WebApp/WebApp.csproj +++ b/src/WebApp/WebApp.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true WebApp Exe @@ -9,9 +9,8 @@ - - - + + diff --git a/src/WebAppSecuredWithJwt/Startup.cs b/src/WebAppSecuredWithJwt/Startup.cs index 356556c3..a68840d0 100644 --- a/src/WebAppSecuredWithJwt/Startup.cs +++ b/src/WebAppSecuredWithJwt/Startup.cs @@ -48,10 +48,8 @@ public void ConfigureServices(IServiceCollection services) // don't worry about this, our JwtSecurityStub is gonna switch it off in // tests - options.Authority = "https://localhost:5010"; + options.Authority = "https://localhost:5001"; - - options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, @@ -59,6 +57,7 @@ public void ConfigureServices(IServiceCollection services) NameClaimType = ClaimTypes.NameIdentifier }; }); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/WebAppSecuredWithJwt/WebAppSecuredWithJwt.csproj b/src/WebAppSecuredWithJwt/WebAppSecuredWithJwt.csproj index f218ace4..ebd9567e 100644 --- a/src/WebAppSecuredWithJwt/WebAppSecuredWithJwt.csproj +++ b/src/WebAppSecuredWithJwt/WebAppSecuredWithJwt.csproj @@ -1,13 +1,12 @@  - net6.0 + net8.0 - - - + +