From ba59794eff1fb270a5d1541de21c6e5126dde448 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Fri, 3 Jan 2025 19:43:14 +0000 Subject: [PATCH 1/8] net9.0 support added --- LICENSE.txt | 2 +- README.md | 3 ++- .../SampleWebApi_7_0/SampleWebApi_7_0.csproj | 1 + .../AspNetCore.Authentication.Basic.csproj | 17 ++++++++--------- .../BasicHandler.cs | 6 ++++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index cf26481..7414cdd 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Mihir Dilip +Copyright (c) 2025 Mihir Dilip Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0ca4a26..8e81a6b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Easy to use and very light weight Microsoft style Basic Scheme Authentication Im ## .NET (Core) Frameworks Supported .NET Framework 4.6.1 and/or NetStandard 2.0 onwards -Multi targeted: net8.0; net7.0; net6.0; net5.0; netcoreapp3.1; netcoreapp3.0; netstandard2.0; net461 +Multi targeted: net9.0; net8.0; net7.0; net6.0; net5.0; netcoreapp3.1; netcoreapp3.0; netstandard2.0; net461
@@ -300,6 +300,7 @@ public void ConfigureServices(IServiceCollection services) ## Release Notes | Version |           Notes | |---------|-------| +|9.0.0 | | |8.0.0 | | |7.0.0 | | |6.0.1 | | diff --git a/samples/SampleWebApi_7_0/SampleWebApi_7_0.csproj b/samples/SampleWebApi_7_0/SampleWebApi_7_0.csproj index 7b6bf52..5d4bfc2 100644 --- a/samples/SampleWebApi_7_0/SampleWebApi_7_0.csproj +++ b/samples/SampleWebApi_7_0/SampleWebApi_7_0.csproj @@ -4,6 +4,7 @@ net7.0 enable enable + false diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index 7d92820..1b0eb53 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -1,20 +1,19 @@  - net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 - 8.0.0 + net8.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 + 9.0.0 https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) - aspnetcore, security, authentication, microsoft, microsoft.aspnetcore.authentication, microsoft-aspnetcore-authentication, microsoft.aspnetcore.authentication.basic, microsoft-aspnetcore-authentication-basic, asp-net-core, netstandard, netstandard20, basic-authentication, basicauthentication, dotnetcore, dotnetcore3.1, net5, net5.0, net6, net6.0, net7, net7.0, net8, net8.0, asp-net-core-basic-authentication, aspnetcore-basic-authentication, asp-net-core-authentication, aspnetcore-authentication, asp, aspnet, basic, authentication-scheme - - net8.0 support added -- Sample project for net8.0 added -- BasicSamplesClient.http file added for testing sample projects + aspnetcore, security, authentication, microsoft, microsoft.aspnetcore.authentication, microsoft-aspnetcore-authentication, microsoft.aspnetcore.authentication.basic, microsoft-aspnetcore-authentication-basic, asp-net-core, netstandard, netstandard20, basic-authentication, basicauthentication, dotnetcore, dotnetcore3.1, net5, net5.0, net6, net6.0, net7, net7.0, net8, net8.0, net9, net9.0, asp-net-core-basic-authentication, aspnetcore-basic-authentication, asp-net-core-authentication, aspnetcore-authentication, asp, aspnet, basic, authentication-scheme + - net9.0 support added +- Sample project for net9.0 added - Readme updated Easy to use and very light weight Microsoft style Basic Scheme Authentication implementation for ASP.NET Core. Mihir Dilip Mihir Dilip - Copyright (c) 2024 Mihir Dilip + Copyright (c) 2025 Mihir Dilip true $(AssemblyName) git @@ -30,7 +29,7 @@ - + false @@ -71,7 +70,7 @@ - + diff --git a/src/AspNetCore.Authentication.Basic/BasicHandler.cs b/src/AspNetCore.Authentication.Basic/BasicHandler.cs index 50bd43c..10bfffd 100644 --- a/src/AspNetCore.Authentication.Basic/BasicHandler.cs +++ b/src/AspNetCore.Authentication.Basic/BasicHandler.cs @@ -31,9 +31,8 @@ protected BasicHandler(IOptionsMonitor options, ILoggerFactory log : base(options, logger, encoder) { } - - [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] #endif + /// /// Basic Handler Constructor. /// @@ -41,6 +40,9 @@ protected BasicHandler(IOptionsMonitor options, ILoggerFactory log /// /// /// +#if NET8_0_OR_GREATER + [Obsolete("ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.")] +#endif public BasicHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { From 09aa6a33194a799c4f8c4b2e468079ea97f1ba34 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Fri, 3 Jan 2025 20:48:28 +0000 Subject: [PATCH 2/8] sample project for net9.0 added --- .../Controllers/ValuesController.cs | 34 ++++ samples/SampleWebApi_9_0/Program.cs | 157 ++++++++++++++++++ .../Properties/launchSettings.json | 21 +++ .../SampleWebApi_9_0/SampleWebApi_9_0.csproj | 19 +++ .../appsettings.Development.json | 8 + samples/SampleWebApi_9_0/appsettings.json | 9 + src/AspNetCore.Authentication.Basic.sln | 8 + .../AspNetCore.Authentication.Basic.csproj | 2 +- 8 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 samples/SampleWebApi_9_0/Controllers/ValuesController.cs create mode 100644 samples/SampleWebApi_9_0/Program.cs create mode 100644 samples/SampleWebApi_9_0/Properties/launchSettings.json create mode 100644 samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj create mode 100644 samples/SampleWebApi_9_0/appsettings.Development.json create mode 100644 samples/SampleWebApi_9_0/appsettings.json diff --git a/samples/SampleWebApi_9_0/Controllers/ValuesController.cs b/samples/SampleWebApi_9_0/Controllers/ValuesController.cs new file mode 100644 index 0000000..99e93f3 --- /dev/null +++ b/samples/SampleWebApi_9_0/Controllers/ValuesController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using System.Text; + +namespace SampleWebApi_9_0.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + [HttpGet("claims")] + public ActionResult Claims() + { + var sb = new StringBuilder(); + foreach (var claim in User.Claims) + { + sb.AppendLine($"{claim.Type}: {claim.Value}"); + } + return sb.ToString(); + } + + [HttpGet("forbid")] + public new IActionResult Forbid() + { + return base.Forbid(); + } + } +} diff --git a/samples/SampleWebApi_9_0/Program.cs b/samples/SampleWebApi_9_0/Program.cs new file mode 100644 index 0000000..3994b87 --- /dev/null +++ b/samples/SampleWebApi_9_0/Program.cs @@ -0,0 +1,157 @@ +using AspNetCore.Authentication.Basic; +using Microsoft.AspNetCore.Authorization; +using SampleWebApi.Repositories; +using SampleWebApi.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add User repository to the dependency container. +builder.Services.AddTransient(); + +// Add the Basic scheme authentication here.. +// It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. +// If an implementation of IBasicUserValidationService interface is registered in the dependency register as well as OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of an implementation of IBasicUserValidationService. +builder.Services.AddAuthentication(BasicDefaults.AuthenticationScheme) + + // The below AddBasic without type parameter will require OnValidateCredentials delegete on options.Events to be set unless an implementation of IBasicUserValidationService interface is registered in the dependency register. + // Please note if both the delgate and validation server are set then the delegate will be used instead of BasicUserValidationService. + //.AddBasic(options => + + // The below AddBasic with type parameter will add the BasicUserValidationService to the dependency register. + // Please note if OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of BasicUserValidationService. + .AddBasic(options => + { + options.Realm = "Sample Web API"; + + //// Optional option to suppress the browser login dialog for ajax calls. + //options.SuppressWWWAuthenticateHeader = true; + + //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. + //options.IgnoreAuthenticationIfAllowAnonymous = true; + + //// Optional events to override the basic original logic with custom logic. + //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. + options.Events = new BasicEvents + { + + //// A delegate assigned to this property will be invoked just before validating credentials. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + // context.Success(); + // } + // else + // { + // context.NoResult(); + // } + //}, + + //// A delegate assigned to this property will be invoked just before validating credentials. + //// NOTE: Same as above delegate but slightly different implementation which will give same result. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.ValidationSucceeded(claims); // claims are optional + // } + // else + // { + // context.ValidationFailed(); + // } + //}, + + //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. + //OnHandleChallenge = async (context) => + //{ + // // custom code to handle authentication challenge unauthorized response. + // context.Response.StatusCode = StatusCodes.Status401Unauthorized; + // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. + //OnHandleForbidden = async (context) => + //{ + // // custom code to handle forbidden response. + // context.Response.StatusCode = StatusCodes.Status403Forbidden; + // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateCredentials delegate is assigned. + //// It can be used for adding claims, headers, etc to the response. + //OnAuthenticationSucceeded = (context) => + //{ + // //custom code to add extra bits to the success response. + // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); + // var customClaims = new List + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") + // }; + // context.AddClaims(customClaims); + // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); + // return Task.CompletedTask; + //}, + + //// A delegate assigned to this property will be invoked when the authentication fails. + //OnAuthenticationFailed = (context) => + //{ + // // custom code to handle failed authentication. + // context.Fail("Failed to authenticate"); + // return Task.CompletedTask; + //} + + }; + }); + +builder.Services.AddControllers(options => +{ + // ALWAYS USE HTTPS (SSL) protocol in production when using ApiKey authentication. + //options.Filters.Add(); + +}); //.AddXmlSerializerFormatters() // To enable XML along with JSON; + +// All the requests will need to be authorized. +// Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. +builder.Services.AddAuthorizationBuilder() + .SetFallbackPolicy( + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build() + ); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/samples/SampleWebApi_9_0/Properties/launchSettings.json b/samples/SampleWebApi_9_0/Properties/launchSettings.json new file mode 100644 index 0000000..77f3a81 --- /dev/null +++ b/samples/SampleWebApi_9_0/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3920", + "sslPort": 44304 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj b/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj new file mode 100644 index 0000000..257440e --- /dev/null +++ b/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj @@ -0,0 +1,19 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/samples/SampleWebApi_9_0/appsettings.Development.json b/samples/SampleWebApi_9_0/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/SampleWebApi_9_0/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/SampleWebApi_9_0/appsettings.json b/samples/SampleWebApi_9_0/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/SampleWebApi_9_0/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/AspNetCore.Authentication.Basic.sln b/src/AspNetCore.Authentication.Basic.sln index abf1601..e159288 100644 --- a/src/AspNetCore.Authentication.Basic.sln +++ b/src/AspNetCore.Authentication.Basic.sln @@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebApi_7_0", "..\samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_8_0", "..\samples\SampleWebApi_8_0\SampleWebApi_8_0.csproj", "{548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_9_0", "..\samples\SampleWebApi_9_0\SampleWebApi_9_0.csproj", "{53DBA160-9C57-4A01-A189-34FD4D052CD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +82,10 @@ Global {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF}.Release|Any CPU.Build.0 = Release|Any CPU + {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -94,6 +100,7 @@ Global {9232DA41-CA69-4FE3-B0C9-D8D85FEC272A} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} {D5C8BCC5-C997-475E-9E71-5BF807294BB6} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} + {53DBA160-9C57-4A01-A189-34FD4D052CD1} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {70815049-1680-480A-BF5A-00536D6C9C20} @@ -101,6 +108,7 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{0801aed9-ea38-4e7e-af4d-26e9b67e5254}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{2705db4c-3bce-4cfc-9a30-b4bfd1f28c56}*SharedItemsImports = 5 + ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{53dba160-9c57-4a01-a189-34fd4d052cd1}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{548dbe4a-0c06-4fb3-bea6-6ab4a0386eef}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{897e5c9c-8c0a-4fb6-960c-4d11aafd4491}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{9232da41-ca69-4fe3-b0c9-d8d85fec272a}*SharedItemsImports = 5 diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index 1b0eb53..416cb64 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -1,7 +1,7 @@  - net8.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 + net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netstandard2.0;net461 9.0.0 https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) https://github.com/mihirdilip/aspnetcore-authentication-basic/tree/$(Version) From b49fa91cde02ba15a81b8684c8fb8ec6ba4297a2 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Fri, 3 Jan 2025 22:15:45 +0000 Subject: [PATCH 3/8] nullable enabled and fixed styling inline with latest language version --- .../AspNetCore.Authentication.Basic.csproj | 4 +++- .../BasicExtensions.cs | 12 +++++----- .../BasicHandler.cs | 24 ++++++++++++------- .../BasicOptions.cs | 6 ++--- .../BasicPostConfigureOptions.cs | 2 +- .../BasicUtils.cs | 2 +- .../BasicAuthenticationSucceededContext.cs | 6 ++--- .../Events/BasicEvents.cs | 10 ++++---- .../Events/BasicValidateCredentialsContext.cs | 4 ++-- .../GlobalSuppressions.cs | 10 ++++++++ .../BasicExtensionsTests.cs | 3 --- .../BasicHandlerTests.cs | 6 +---- .../BasicOptionsTests.cs | 3 --- .../BasicPostConfigureOptionsTests.cs | 10 ++++---- .../BasicUtilsTests.cs | 17 ++++++------- .../BasicAuthenticationFailedContextTests.cs | 6 ++--- ...asicAuthenticationSucceededContextTests.cs | 15 +++++------- .../BasicHandleChallengeContextTests.cs | 6 ++--- .../BasicHandleForbiddenContextTests.cs | 5 +--- .../BasicValidateCredentialsContextTests.cs | 10 +++----- .../Infrastructure/ClaimsPrincipalDto.cs | 3 --- .../FakeBasicUserValidationService.cs | 10 +++----- .../Infrastructure/TestServerBuilder.cs | 3 --- 23 files changed, 78 insertions(+), 99 deletions(-) create mode 100644 src/AspNetCore.Authentication.Basic/GlobalSuppressions.cs diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index 416cb64..1830997 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -17,8 +17,10 @@ true $(AssemblyName) git - + latest + enable + true LICENSE.txt README.md diff --git a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs index 084a8a3..93a46a5 100644 --- a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs @@ -40,7 +40,7 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, /// /// The configure options. /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action configureOptions) + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action? configureOptions) => builder.AddBasic(BasicDefaults.AuthenticationScheme, configureOptions); /// @@ -51,7 +51,7 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, /// The authentication scheme. /// The configure options. /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action? configureOptions) => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions); /// @@ -63,7 +63,7 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, /// The display name. /// The configure options. /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action? configureOptions) { // Adds post configure options to the pipeline. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, BasicPostConfigureOptions>()); @@ -107,7 +107,7 @@ public static AuthenticationBuilder AddBasic(this A /// /// The . /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(BasicDefaults.AuthenticationScheme, configureOptions); /// @@ -119,7 +119,7 @@ public static AuthenticationBuilder AddBasic(this A /// The authentication scheme. /// The . /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions); /// @@ -132,7 +132,7 @@ public static AuthenticationBuilder AddBasic(this A /// The display name. /// The . /// The instance of - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService { // Adds implementation of IBasicUserValidationService to the dependency container. diff --git a/src/AspNetCore.Authentication.Basic/BasicHandler.cs b/src/AspNetCore.Authentication.Basic/BasicHandler.cs index 10bfffd..ed1bf98 100644 --- a/src/AspNetCore.Authentication.Basic/BasicHandler.cs +++ b/src/AspNetCore.Authentication.Basic/BasicHandler.cs @@ -53,7 +53,7 @@ public BasicHandler(IOptionsMonitor options, ILoggerFactory logger /// /// Get or set . /// - protected new BasicEvents Events { get => (BasicEvents)base.Events; set => base.Events = value; } + protected new BasicEvents Events { get => (BasicEvents)base.Events!; set => base.Events = value; } /// /// Create an instance of . @@ -91,6 +91,12 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } + if (string.IsNullOrWhiteSpace(headerValue.Parameter)) + { + Logger.LogInformation($"'Authorization' header found but the scheme value/credentials is not present."); + return AuthenticateResult.NoResult(); + } + BasicCredentials credentials; try { @@ -172,7 +178,7 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop await base.HandleChallengeAsync(properties); } - private async Task RaiseAndHandleEventValidateCredentialsAsync(BasicCredentials credentials) + private async Task RaiseAndHandleEventValidateCredentialsAsync(BasicCredentials credentials) { var validateCredentialsContext = new BasicValidateCredentialsContext(Context, Scheme, Options, credentials.Username, credentials.Password); await Events.ValidateCredentialsAsync(validateCredentialsContext).ConfigureAwait(false); @@ -210,7 +216,10 @@ private async Task RaiseAndHandleAuthenticationSucceededAsyn { // If claims principal is set and is authenticated then build a ticket by calling and return success. authenticationSucceededContext.Success(); - return authenticationSucceededContext.Result; + if (authenticationSucceededContext.Result != null) + { + return authenticationSucceededContext.Result; + } } Logger.LogError("No authenticated prinicipal set."); @@ -229,7 +238,7 @@ private bool IgnoreAuthenticationIfAllowAnonymous() private async Task ValidateUsingBasicUserValidationServiceAsync(string username, string password) { - IBasicUserValidationService basicUserValidationService = null; + IBasicUserValidationService? basicUserValidationService = null; if (Options.BasicUserValidationServiceType != null) { basicUserValidationService = ActivatorUtilities.GetServiceOrCreateInstance(Context.RequestServices, Options.BasicUserValidationServiceType) as IBasicUserValidationService; @@ -279,10 +288,7 @@ private static BasicCredentials DecodeBasicCredentials(string credentials) throw new Exception("Username cannot be empty."); } - if (password == null) - { - password = string.Empty; - } + password ??= string.Empty; return new BasicCredentials(username, password); } @@ -290,7 +296,7 @@ private static BasicCredentials DecodeBasicCredentials(string credentials) private readonly struct BasicCredentials { public BasicCredentials(string username, string password) - { + { Username = username; Password = password; } diff --git a/src/AspNetCore.Authentication.Basic/BasicOptions.cs b/src/AspNetCore.Authentication.Basic/BasicOptions.cs index 15ac166..a64ae3d 100644 --- a/src/AspNetCore.Authentication.Basic/BasicOptions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicOptions.cs @@ -24,7 +24,7 @@ public BasicOptions() /// Required to be set if SuppressWWWAuthenticateHeader is not set to true. /// /// - public string Realm { get; set; } + public string? Realm { get; set; } /// /// Default value is false. @@ -42,7 +42,7 @@ public BasicOptions() /// public new BasicEvents Events { - get => (BasicEvents)base.Events; + get => (BasicEvents)base.Events!; set => base.Events = value; } @@ -54,6 +54,6 @@ public BasicOptions() public bool IgnoreAuthenticationIfAllowAnonymous { get; set; } #endif - internal Type BasicUserValidationServiceType { get; set; } = null; + internal Type? BasicUserValidationServiceType { get; set; } = null; } } diff --git a/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs b/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs index d04ec65..163606d 100644 --- a/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs @@ -11,7 +11,7 @@ namespace AspNetCore.Authentication.Basic /// internal class BasicPostConfigureOptions : IPostConfigureOptions { - public void PostConfigure(string name, BasicOptions options) + public void PostConfigure(string? name, BasicOptions options) { if (!options.SuppressWWWAuthenticateHeader && string.IsNullOrWhiteSpace(options.Realm)) { diff --git a/src/AspNetCore.Authentication.Basic/BasicUtils.cs b/src/AspNetCore.Authentication.Basic/BasicUtils.cs index 60e97d3..bf69b68 100644 --- a/src/AspNetCore.Authentication.Basic/BasicUtils.cs +++ b/src/AspNetCore.Authentication.Basic/BasicUtils.cs @@ -23,7 +23,7 @@ internal static class BasicUtils /// The claims issuer. /// The list of claims. /// - internal static ClaimsPrincipal BuildClaimsPrincipal(string username, string schemeName, string claimsIssuer, IEnumerable claims = null) + internal static ClaimsPrincipal BuildClaimsPrincipal(string? username, string schemeName, string? claimsIssuer, IEnumerable? claims = null) { if (string.IsNullOrWhiteSpace(schemeName)) throw new ArgumentNullException(nameof(schemeName)); diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs index 7fd79dc..b6ab0ad 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs @@ -30,7 +30,7 @@ public BasicAuthenticationSucceededContext(HttpContext context, AuthenticationSc /// /// Get the containing the user claims. /// - public new ClaimsPrincipal Principal => base.Principal; + public new ClaimsPrincipal Principal => base.Principal!; /// /// Called to replace the claims principal. The supplied principal will replace the value of the @@ -57,7 +57,7 @@ public void ReplacePrincipal(ClaimsPrincipal principal) public void AddClaim(Claim claim) { if (claim == null) throw new ArgumentNullException(nameof(claim)); - (Principal?.Identity as ClaimsIdentity).AddClaim(claim); + (Principal.Identity as ClaimsIdentity)?.AddClaim(claim); } /// @@ -68,7 +68,7 @@ public void AddClaim(Claim claim) public void AddClaims(IEnumerable claims) { if (claims == null) throw new ArgumentNullException(nameof(claims)); - (Principal?.Identity as ClaimsIdentity).AddClaims(claims); + (Principal.Identity as ClaimsIdentity)?.AddClaims(claims); } } } diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs b/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs index 513a79a..2b784c7 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs @@ -20,7 +20,7 @@ public class BasicEvents /// or construct an authentication principal from the user details & attach it to the context.Principal property and finally call context.Success() method. /// If only context.Principal property set without calling context.Success() method then, Success() method is automaticalled called. /// - public Func OnValidateCredentials { get; set; } + public Func? OnValidateCredentials { get; set; } /// /// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateCredentials delegate is assigned. @@ -29,12 +29,12 @@ public class BasicEvents /// /// Only use this if you know what you are doing. /// - public Func OnAuthenticationSucceeded { get; set; } + public Func? OnAuthenticationSucceeded { get; set; } /// /// A delegate assigned to this property will be invoked when the authentication fails. /// - public Func OnAuthenticationFailed { get; set; } + public Func? OnAuthenticationFailed { get; set; } /// /// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. @@ -46,7 +46,7 @@ public class BasicEvents /// changing the 401 result to 302 of a login page or external sign-in location.) /// Call context.Handled() at the end so that any default logic for this challenge will be skipped. /// - public Func OnHandleChallenge { get; set; } + public Func? OnHandleChallenge { get; set; } /// /// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. @@ -56,7 +56,7 @@ public class BasicEvents /// Set the delegate to handle Forbid. /// Call context.Handled() at the end so that any default logic will be skipped. /// - public Func OnHandleForbidden { get; set; } + public Func? OnHandleForbidden { get; set; } diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs b/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs index 923bbd3..b840547 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs @@ -45,7 +45,7 @@ public BasicValidateCredentialsContext(HttpContext context, AuthenticationScheme /// and method will also be called. /// /// Claims to be added to the identity. - public void ValidationSucceeded(IEnumerable claims = null) + public void ValidationSucceeded(IEnumerable? claims = null) { Principal = BasicUtils.BuildClaimsPrincipal(Username, Scheme.Name, Options.ClaimsIssuer, claims); Success(); @@ -56,7 +56,7 @@ public void ValidationSucceeded(IEnumerable claims = null) /// otherwise, method will be called. /// /// (Optional) The failure message. - public void ValidationFailed(string failureMessage = null) + public void ValidationFailed(string? failureMessage = null) { if (string.IsNullOrWhiteSpace(failureMessage)) { diff --git a/src/AspNetCore.Authentication.Basic/GlobalSuppressions.cs b/src/AspNetCore.Authentication.Basic/GlobalSuppressions.cs new file mode 100644 index 0000000..f487af1 --- /dev/null +++ b/src/AspNetCore.Authentication.Basic/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "Does not need to match, folder used for structural purpose only.", Scope = "namespace", Target = "~N:AspNetCore.Authentication.Basic")] +[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "Not a fan of this.")] +[assembly: SuppressMessage("Maintainability", "CA1510:Use ArgumentNullException throw helper", Justification = "Not supported by older frameworks.")] diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs index db5e24a..8437d34 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicExtensionsTests.cs @@ -3,9 +3,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Linq; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs index eeff448..4982e4f 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicHandlerTests.cs @@ -7,15 +7,11 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Json; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests @@ -591,7 +587,7 @@ public async Task MultiScheme() #endregion // Multi-Scheme - private async Task DeserializeClaimsPrincipalAsync(HttpResponseMessage response) + private static async Task DeserializeClaimsPrincipalAsync(HttpResponseMessage response) { return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); } diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs index 47a4abf..16d70b9 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicOptionsTests.cs @@ -4,10 +4,7 @@ using AspNetCore.Authentication.Basic.Tests.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicPostConfigureOptionsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicPostConfigureOptionsTests.cs index 8ec6924..5f220b1 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicPostConfigureOptionsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicPostConfigureOptionsTests.cs @@ -2,14 +2,12 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCore.Authentication.Basic.Tests.Infrastructure; -using System; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests { - public class BasicPostConfigureOptionsTests - { + public class BasicPostConfigureOptionsTests + { [Fact] public async Task PostConfigure_no_option_set_throws_exception() { @@ -77,13 +75,13 @@ await RunAuthInitWithServiceAsync(options => } - private async Task RunAuthInitAsync(Action configureOptions) + private static async Task RunAuthInitAsync(Action configureOptions) { var server = TestServerBuilder.BuildTestServer(configureOptions); await server.CreateClient().GetAsync(TestServerBuilder.BaseUrl); } - private async Task RunAuthInitWithServiceAsync(Action configureOptions) + private static async Task RunAuthInitWithServiceAsync(Action configureOptions) { var server = TestServerBuilder.BuildTestServerWithService(configureOptions); await server.CreateClient().GetAsync(TestServerBuilder.BaseUrl); diff --git a/test/AspNetCore.Authentication.Basic.Tests/BasicUtilsTests.cs b/test/AspNetCore.Authentication.Basic.Tests/BasicUtilsTests.cs index dc92e9d..9f9fa96 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/BasicUtilsTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/BasicUtilsTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using Xunit; @@ -21,7 +18,7 @@ public static void BuildClaimsPrincipal_null_ownerName_no_exception() [Fact] public static void BuildClaimsPrincipal_null_schemeName_throws_ArgumentNullException() { - var exception = Assert.Throws(() => BasicUtils.BuildClaimsPrincipal(null, null, null, null)); + var exception = Assert.Throws(() => BasicUtils.BuildClaimsPrincipal(null, null!, null, null)); Assert.Contains("schemeName", exception.Message); } @@ -57,8 +54,8 @@ public static void BuildClaimsPrincipal_adds_single_identity_with_claims() var schemeName = "Test"; var claims = new List { - new Claim(ClaimTypes.Email, "abc@xyz.com") , - new Claim(ClaimTypes.Role, "admin") + new(ClaimTypes.Email, "abc@xyz.com") , + new(ClaimTypes.Role, "admin") }; var claimsPrincipal = BasicUtils.BuildClaimsPrincipal(null, schemeName, null, claims); Assert.NotNull(claimsPrincipal); @@ -89,8 +86,8 @@ public static void BuildClaimsPrincipal_ownerName_adds_Name_and_NameIdentifier_c var schemeName = "Test"; var claims = new List { - new Claim(ClaimTypes.Email, "abc@xyz.com") , - new Claim(ClaimTypes.Role, "admin") + new(ClaimTypes.Email, "abc@xyz.com") , + new(ClaimTypes.Role, "admin") }; var claimsPrincipal = BasicUtils.BuildClaimsPrincipal(ownerName, schemeName, null, claims); Assert.NotNull(claimsPrincipal); @@ -108,8 +105,8 @@ public static void BuildClaimsPrincipal_ownerName_adds_Name_and_NameIdentifier_c var schemeName = "Test"; var claims = new List { - new Claim(ClaimTypes.Name, "Admin"), - new Claim(ClaimTypes.Role, "admin") + new(ClaimTypes.Name, "Admin"), + new(ClaimTypes.Role, "admin") }; var claimsPrincipal = BasicUtils.BuildClaimsPrincipal(ownerName, schemeName, null, claims); Assert.NotNull(claimsPrincipal); diff --git a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationFailedContextTests.cs b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationFailedContextTests.cs index f0f53b1..cc5948a 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationFailedContextTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationFailedContextTests.cs @@ -2,16 +2,14 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using AspNetCore.Authentication.Basic.Tests.Infrastructure; -using System; using System.Net; using System.Net.Http; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests.Events { - public class BasicAuthenticationFailedContext - { + public class BasicAuthenticationFailedContext + { private static readonly string ExpectedExceptionMessage = $"Either {nameof(BasicOptions.Events.OnValidateCredentials)} delegate on configure options {nameof(BasicOptions.Events)} should be set or use an extention method with type parameter of type {nameof(IBasicUserValidationService)}."; [Fact] diff --git a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationSucceededContextTests.cs b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationSucceededContextTests.cs index a29d0c8..54e0168 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationSucceededContextTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicAuthenticationSucceededContextTests.cs @@ -3,20 +3,17 @@ using AspNetCore.Authentication.Basic.Tests.Infrastructure; using Microsoft.AspNetCore.TestHost; -using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Security.Claims; using System.Text.Json; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests.Events { public class BasicAuthenticationSucceededContextTests : IDisposable { - private readonly List _serversToDispose = new List(); + private readonly List _serversToDispose = []; public void Dispose() { @@ -45,7 +42,7 @@ public async Task ReplacePrincipal_null_throws_argument_null_exception() using var client = BuildClient( context => { - Assert.Throws(() => context.ReplacePrincipal(null)); + Assert.Throws(() => context.ReplacePrincipal(null!)); return Task.CompletedTask; } ); @@ -113,8 +110,8 @@ public async Task AddClaim() public async Task AddClaims() { var claims = new List{ - new Claim(ClaimTypes.Actor, "Actor"), - new Claim(ClaimTypes.Country, "Country") + new(ClaimTypes.Actor, "Actor"), + new(ClaimTypes.Country, "Country") }; using var client = BuildClient( @@ -148,7 +145,7 @@ private HttpClient BuildClient(Func o return server.CreateClient(); } - private async Task RunUnauthorizedTests(HttpClient client) + private static async Task RunUnauthorizedTests(HttpClient client) { using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl); request.Headers.Authorization = FakeUsers.FakeUser.ToAuthenticationHeaderValue(); @@ -157,7 +154,7 @@ private async Task RunUnauthorizedTests(HttpClient client) Assert.Equal(HttpStatusCode.Unauthorized, response_unauthorized.StatusCode); } - private async Task RunSuccessTests(HttpClient client) + private static async Task RunSuccessTests(HttpClient client) { using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl); request.Headers.Authorization = FakeUsers.FakeUser.ToAuthenticationHeaderValue(); diff --git a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleChallengeContextTests.cs b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleChallengeContextTests.cs index 01722f8..f993409 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleChallengeContextTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleChallengeContextTests.cs @@ -4,22 +4,20 @@ using AspNetCore.Authentication.Basic.Tests.Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; -using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests.Events { public class BasicHandleChallengeContextTests : IDisposable { - private readonly List _serversToDispose = new List(); + private readonly List _serversToDispose = []; public void Dispose() { _serversToDispose.ForEach(s => s.Dispose()); + GC.SuppressFinalize(this); } [Fact] diff --git a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleForbiddenContextTests.cs b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleForbiddenContextTests.cs index c1663df..0895e05 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleForbiddenContextTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicHandleForbiddenContextTests.cs @@ -4,18 +4,15 @@ using AspNetCore.Authentication.Basic.Tests.Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; -using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests.Events { public class BasicHandleForbiddenContextTests : IDisposable { - private readonly List _serversToDispose = new List(); + private readonly List _serversToDispose = []; public void Dispose() { diff --git a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicValidateCredentialsContextTests.cs b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicValidateCredentialsContextTests.cs index 7dfff55..d445232 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Events/BasicValidateCredentialsContextTests.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Events/BasicValidateCredentialsContextTests.cs @@ -3,21 +3,17 @@ using AspNetCore.Authentication.Basic.Tests.Infrastructure; using Microsoft.AspNetCore.TestHost; -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Security.Claims; using System.Text.Json; -using System.Threading.Tasks; using Xunit; namespace AspNetCore.Authentication.Basic.Tests.Events { public class BasicValidateCredentialsContextTests : IDisposable { - private readonly List _serversToDispose = new List(); + private readonly List _serversToDispose = []; public void Dispose() { @@ -190,14 +186,14 @@ private HttpClient BuildClient(Func onVal return server.CreateClient(); } - private async Task RunUnauthorizedTests(HttpClient client) + private static async Task RunUnauthorizedTests(HttpClient client) { using var response_unauthorized = await client.GetAsync(TestServerBuilder.ClaimsPrincipalUrl); Assert.False(response_unauthorized.IsSuccessStatusCode); Assert.Equal(HttpStatusCode.Unauthorized, response_unauthorized.StatusCode); } - private async Task RunSuccessTests(HttpClient client) + private static async Task RunSuccessTests(HttpClient client) { using var request = new HttpRequestMessage(HttpMethod.Get, TestServerBuilder.ClaimsPrincipalUrl); request.Headers.Authorization = FakeUsers.FakeUser.ToAuthenticationHeaderValue(); diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs index 543b870..5640c93 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/ClaimsPrincipalDto.cs @@ -1,9 +1,6 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Security.Principal; diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs index 273eec4..ccad124 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/FakeBasicUserValidationService.cs @@ -1,13 +1,9 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; -using System.Threading.Tasks; namespace AspNetCore.Authentication.Basic.Tests.Infrastructure { @@ -64,12 +60,12 @@ class FakeUsers internal static User FakeUserThrowsNotImplemented => new(FakeUserName, FakePasswordThrowsNotImplemented, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); internal static User FakeUserIgnoreAuthenticationIfAllowAnonymous => new(FakeUserName, FakePasswordIgnoreAuthenticationIfAllowAnonymous, new List { FakeNameClaim, FakeNameIdentifierClaim, FakeRoleClaim }); - internal static List Users => new() - { + internal static List Users => + [ FakeUser, FakeUserWithEmptyPassword, FakeUserThrowsNotImplemented, FakeUserIgnoreAuthenticationIfAllowAnonymous - }; + ]; } } diff --git a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs index 7c9e919..c1cd944 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/Infrastructure/TestServerBuilder.cs @@ -9,10 +9,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; -using System; -using System.Linq; using System.Text.Json; -using System.Threading.Tasks; namespace AspNetCore.Authentication.Basic.Tests.Infrastructure { From a891ee5d4f752dedbee1a73f759c68ce0251af5f Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Fri, 3 Jan 2025 22:34:14 +0000 Subject: [PATCH 4/8] test project updated to add support for net9.0 and nuget packages updated --- ...pNetCore.Authentication.Basic.Tests.csproj | 27 ++++++++++++------- .../GlobalSuppressions.cs | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj b/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj index d4e93aa..70fcddb 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj +++ b/test/AspNetCore.Authentication.Basic.Tests/AspNetCore.Authentication.Basic.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.1;net461 + net9.0;net8.0;net7.0;net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.1;net461 enable enable false @@ -21,38 +21,47 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs b/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs index 7f3f9c8..5d53424 100644 --- a/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs +++ b/test/AspNetCore.Authentication.Basic.Tests/GlobalSuppressions.cs @@ -9,3 +9,4 @@ [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.ClaimsPrincipalDto.#ctor(System.Security.Claims.ClaimsPrincipal)")] [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.ClaimDto.#ctor(System.Security.Claims.Claim)")] [assembly: SuppressMessage("Usage", "ASP0025:Use AddAuthorizationBuilder", Justification = "", Scope = "member", Target = "~M:AspNetCore.Authentication.Basic.Tests.Infrastructure.TestServerBuilder.BuildTestServer(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder})~Microsoft.AspNetCore.TestHost.TestServer")] +[assembly: SuppressMessage("Assertions", "xUnit2031:Do not use Where clause with Assert.Single", Justification = "Not supported by older frameworks.")] From 5972e5f2c753201b6cc74e84384d2d6710110be9 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Fri, 3 Jan 2025 22:54:44 +0000 Subject: [PATCH 5/8] implicit usings enabled --- README.md | 2 +- .../AspNetCore.Authentication.Basic.csproj | 2 +- src/AspNetCore.Authentication.Basic/BasicExtensions.cs | 1 - src/AspNetCore.Authentication.Basic/BasicHandler.cs | 2 -- src/AspNetCore.Authentication.Basic/BasicOptions.cs | 1 - .../BasicPostConfigureOptions.cs | 1 - src/AspNetCore.Authentication.Basic/BasicUtils.cs | 3 --- .../Events/BasicAuthenticationFailedContext.cs | 1 - .../Events/BasicAuthenticationSucceededContext.cs | 2 -- src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs | 3 --- .../Events/BasicValidateCredentialsContext.cs | 2 -- .../IBasicUserValidationService.cs | 2 -- 12 files changed, 2 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 8e81a6b..753637a 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ public void ConfigureServices(IServiceCollection services) ## Release Notes | Version |           Notes | |---------|-------| -|9.0.0 |
  • net9.0 support added
  • Sample project for net9.0 added
  • Readme updated
| +|9.0.0 |
  • net9.0 support added
  • Sample project for net9.0 added
  • Readme updated
  • Nullable reference types enabled
  • Language version set to latest
  • Implicit usings enabled
| |8.0.0 |
  • net8.0 support added
  • Sample project for net8.0 added
  • BasicSamplesClient.http file added for testing sample projects
  • Readme updated
| |7.0.0 |
  • net7.0 support added
  • Information log on handler is changed to Debug log when Authorization header is not found on the request
  • Added package validations
  • Sample project for net7.0 added
  • Readme updated
  • Readme added to package
| |6.0.1 |
  • net6.0 support added
  • Information log on handler is changed to Debug log when IgnoreAuthenticationIfAllowAnonymous is enabled [#9](https://github.com/mihirdilip/aspnetcore-authentication-basic/issues/9)
  • Sample project added
  • Readme updated
  • Copyright year updated on License
| diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index 1830997..99a5435 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -20,7 +20,7 @@ latest enable - + enable true LICENSE.txt README.md diff --git a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs index 93a46a5..b8ff529 100644 --- a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using System; namespace AspNetCore.Authentication.Basic { diff --git a/src/AspNetCore.Authentication.Basic/BasicHandler.cs b/src/AspNetCore.Authentication.Basic/BasicHandler.cs index ed1bf98..281d902 100644 --- a/src/AspNetCore.Authentication.Basic/BasicHandler.cs +++ b/src/AspNetCore.Authentication.Basic/BasicHandler.cs @@ -7,11 +7,9 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using System; using System.Net.Http.Headers; using System.Text; using System.Text.Encodings.Web; -using System.Threading.Tasks; namespace AspNetCore.Authentication.Basic { diff --git a/src/AspNetCore.Authentication.Basic/BasicOptions.cs b/src/AspNetCore.Authentication.Basic/BasicOptions.cs index a64ae3d..6b63521 100644 --- a/src/AspNetCore.Authentication.Basic/BasicOptions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicOptions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Authentication; -using System; namespace AspNetCore.Authentication.Basic { diff --git a/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs b/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs index 163606d..354b689 100644 --- a/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicPostConfigureOptions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Extensions.Options; -using System; namespace AspNetCore.Authentication.Basic { diff --git a/src/AspNetCore.Authentication.Basic/BasicUtils.cs b/src/AspNetCore.Authentication.Basic/BasicUtils.cs index bf69b68..6ae083d 100644 --- a/src/AspNetCore.Authentication.Basic/BasicUtils.cs +++ b/src/AspNetCore.Authentication.Basic/BasicUtils.cs @@ -1,9 +1,6 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Claims; namespace AspNetCore.Authentication.Basic diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationFailedContext.cs b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationFailedContext.cs index 411eb37..594af5b 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationFailedContext.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationFailedContext.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using System; namespace AspNetCore.Authentication.Basic { diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs index b6ab0ad..9157254 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicAuthenticationSucceededContext.cs @@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; using System.Security.Claims; namespace AspNetCore.Authentication.Basic diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs b/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs index 2b784c7..5e8e97b 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicEvents.cs @@ -1,9 +1,6 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Threading.Tasks; - namespace AspNetCore.Authentication.Basic { /// diff --git a/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs b/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs index b840547..ce869d6 100644 --- a/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs +++ b/src/AspNetCore.Authentication.Basic/Events/BasicValidateCredentialsContext.cs @@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; using System.Security.Claims; namespace AspNetCore.Authentication.Basic diff --git a/src/AspNetCore.Authentication.Basic/IBasicUserValidationService.cs b/src/AspNetCore.Authentication.Basic/IBasicUserValidationService.cs index 09df8c4..5526791 100644 --- a/src/AspNetCore.Authentication.Basic/IBasicUserValidationService.cs +++ b/src/AspNetCore.Authentication.Basic/IBasicUserValidationService.cs @@ -1,8 +1,6 @@ // Copyright (c) Mihir Dilip. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System.Threading.Tasks; - namespace AspNetCore.Authentication.Basic { /// From 63f66c5e656ba458a4125139f2610ca492fcd322 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Thu, 9 Jan 2025 22:31:17 +0000 Subject: [PATCH 6/8] AOT support added --- README.md | 2 +- samples/SampleWebApi_AOT/Program.cs | 184 ++++++++++++++++++ .../Properties/launchSettings.json | 17 ++ .../SampleWebApi_AOT/SampleWebApi_AOT.csproj | 22 +++ .../appsettings.Development.json | 8 + samples/SampleWebApi_AOT/appsettings.json | 9 + src/AspNetCore.Authentication.Basic.sln | 8 + .../AspNetCore.Authentication.Basic.csproj | 5 + .../BasicExtensions.cs | 26 +++ .../BasicOptions.cs | 4 + 10 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 samples/SampleWebApi_AOT/Program.cs create mode 100644 samples/SampleWebApi_AOT/Properties/launchSettings.json create mode 100644 samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj create mode 100644 samples/SampleWebApi_AOT/appsettings.Development.json create mode 100644 samples/SampleWebApi_AOT/appsettings.json diff --git a/README.md b/README.md index 753637a..2ba9d23 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ public void ConfigureServices(IServiceCollection services) ## Release Notes | Version |           Notes | |---------|-------| -|9.0.0 |
  • net9.0 support added
  • Sample project for net9.0 added
  • Readme updated
  • Nullable reference types enabled
  • Language version set to latest
  • Implicit usings enabled
| +|9.0.0 |
  • net9.0 support added
  • Sample project for net9.0 added
  • Readme updated
  • Nullable reference types enabled
  • Language version set to latest
  • Implicit usings enabled
  • AOT support added
| |8.0.0 |
  • net8.0 support added
  • Sample project for net8.0 added
  • BasicSamplesClient.http file added for testing sample projects
  • Readme updated
| |7.0.0 |
  • net7.0 support added
  • Information log on handler is changed to Debug log when Authorization header is not found on the request
  • Added package validations
  • Sample project for net7.0 added
  • Readme updated
  • Readme added to package
| |6.0.1 |
  • net6.0 support added
  • Information log on handler is changed to Debug log when IgnoreAuthenticationIfAllowAnonymous is enabled [#9](https://github.com/mihirdilip/aspnetcore-authentication-basic/issues/9)
  • Sample project added
  • Readme updated
  • Copyright year updated on License
| diff --git a/samples/SampleWebApi_AOT/Program.cs b/samples/SampleWebApi_AOT/Program.cs new file mode 100644 index 0000000..2bb5d2d --- /dev/null +++ b/samples/SampleWebApi_AOT/Program.cs @@ -0,0 +1,184 @@ +using AspNetCore.Authentication.Basic; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using SampleWebApi.Repositories; +using SampleWebApi.Services; +using System.Text; +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateSlimBuilder(args); +builder.WebHost.UseKestrelHttpsConfiguration(); + +// Add User repository to the dependency container. +builder.Services.AddTransient(); + +// Add the Basic scheme authentication here.. +// It requires Realm to be set in the options if SuppressWWWAuthenticateHeader is not set. +// If an implementation of IBasicUserValidationService interface is registered in the dependency register as well as OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of an implementation of IBasicUserValidationService. +builder.Services.AddAuthentication(BasicDefaults.AuthenticationScheme) + + // The below AddBasic without type parameter will require OnValidateCredentials delegete on options.Events to be set unless an implementation of IBasicUserValidationService interface is registered in the dependency register. + // Please note if both the delgate and validation server are set then the delegate will be used instead of BasicUserValidationService. + //.AddBasic(options => + + // The below AddBasic with type parameter will add the BasicUserValidationService to the dependency register. + // Please note if OnValidateCredentials delegete on options.Events is also set then this delegate will be used instead of BasicUserValidationService. + .AddBasic(options => + { + options.Realm = "Sample Web API"; + + //// Optional option to suppress the browser login dialog for ajax calls. + //options.SuppressWWWAuthenticateHeader = true; + + //// Optional option to ignore authentication if AllowAnonumous metadata/filter attribute is added to an endpoint. + //options.IgnoreAuthenticationIfAllowAnonymous = true; + + //// Optional events to override the basic original logic with custom logic. + //// Only use this if you know what you are doing at your own risk. Any of the events can be assigned. + options.Events = new BasicEvents + { + + //// A delegate assigned to this property will be invoked just before validating credentials. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer), + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); + // context.Success(); + // } + // else + // { + // context.NoResult(); + // } + //}, + + //// A delegate assigned to this property will be invoked just before validating credentials. + //// NOTE: Same as above delegate but slightly different implementation which will give same result. + //OnValidateCredentials = async (context) => + //{ + // // custom code to handle credentials, create principal and call Success method on context. + // var userRepository = context.HttpContext.RequestServices.GetRequiredService(); + // var user = await userRepository.GetUserByUsername(context.Username); + // var isValid = user != null && user.Password == context.Password; + // if (isValid) + // { + // context.Response.Headers.Add("ValidationCustomHeader", "From OnValidateCredentials"); + // var claims = new[] + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnValidateCredentials") + // }; + // context.ValidationSucceeded(claims); // claims are optional + // } + // else + // { + // context.ValidationFailed(); + // } + //}, + + //// A delegate assigned to this property will be invoked before a challenge is sent back to the caller when handling unauthorized response. + //OnHandleChallenge = async (context) => + //{ + // // custom code to handle authentication challenge unauthorized response. + // context.Response.StatusCode = StatusCodes.Status401Unauthorized; + // context.Response.Headers.Add("ChallengeCustomHeader", "From OnHandleChallenge"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleChallenge\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked if Authorization fails and results in a Forbidden response. + //OnHandleForbidden = async (context) => + //{ + // // custom code to handle forbidden response. + // context.Response.StatusCode = StatusCodes.Status403Forbidden; + // context.Response.Headers.Add("ForbidCustomHeader", "From OnHandleForbidden"); + // await context.Response.WriteAsync("{\"CustomBody\":\"From OnHandleForbidden\"}"); + // context.Handled(); // important! do not forget to call this method at the end. + //}, + + //// A delegate assigned to this property will be invoked when the authentication succeeds. It will not be called if OnValidateCredentials delegate is assigned. + //// It can be used for adding claims, headers, etc to the response. + //OnAuthenticationSucceeded = (context) => + //{ + // //custom code to add extra bits to the success response. + // context.Response.Headers.Add("SuccessCustomHeader", "From OnAuthenticationSucceeded"); + // var customClaims = new List + // { + // new Claim("CustomClaimType", "Custom Claim Value - from OnAuthenticationSucceeded") + // }; + // context.AddClaims(customClaims); + // //or can add like this - context.Principal.AddIdentity(new ClaimsIdentity(customClaims)); + // return Task.CompletedTask; + //}, + + //// A delegate assigned to this property will be invoked when the authentication fails. + //OnAuthenticationFailed = (context) => + //{ + // // custom code to handle failed authentication. + // context.Fail("Failed to authenticate"); + // return Task.CompletedTask; + //} + + }; + }); + +// All the requests will need to be authorized. +// Alternatively, add [Authorize] attribute to Controller or Action Method where necessary. +builder.Services.AddAuthorizationBuilder() + .SetFallbackPolicy( + new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build() + ); + + +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); +}); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthentication(); // NOTE: DEFAULT TEMPLATE DOES NOT HAVE THIS, THIS LINE IS REQUIRED AND HAS TO BE ADDED!!! + +app.UseAuthorization(); + +var valuesApi = app.MapGroup("/api/values") + .RequireAuthorization(); +valuesApi.MapGet("/", () => new[] { "value1", "value2" }); +valuesApi.MapGet("/claims", async (context) => +{ + var sb = new StringBuilder(); + foreach (var claim in context.User.Claims) + { + sb.AppendLine($"{claim.Type}: {claim.Value}"); + } + context.Response.StatusCode = 200; + await context.Response.WriteAsync(sb.ToString()); +}); +valuesApi.MapGet("/forbid", async (context) => +{ + await context.ForbidAsync(); +}); + +app.Run(); + + +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(string[]))] +internal partial class AppJsonSerializerContext : JsonSerializerContext +{ + +} diff --git a/samples/SampleWebApi_AOT/Properties/launchSettings.json b/samples/SampleWebApi_AOT/Properties/launchSettings.json new file mode 100644 index 0000000..1781183 --- /dev/null +++ b/samples/SampleWebApi_AOT/Properties/launchSettings.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:44304;http://localhost:3920", + "sslPort": 44304, + "useSSL": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj b/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj new file mode 100644 index 0000000..233cc84 --- /dev/null +++ b/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + true + + + + + + + + + + + + diff --git a/samples/SampleWebApi_AOT/appsettings.Development.json b/samples/SampleWebApi_AOT/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/samples/SampleWebApi_AOT/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/SampleWebApi_AOT/appsettings.json b/samples/SampleWebApi_AOT/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/samples/SampleWebApi_AOT/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/AspNetCore.Authentication.Basic.sln b/src/AspNetCore.Authentication.Basic.sln index e159288..ca17524 100644 --- a/src/AspNetCore.Authentication.Basic.sln +++ b/src/AspNetCore.Authentication.Basic.sln @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_8_0", "..\samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_9_0", "..\samples\SampleWebApi_9_0\SampleWebApi_9_0.csproj", "{53DBA160-9C57-4A01-A189-34FD4D052CD1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApi_AOT", "..\samples\SampleWebApi_AOT\SampleWebApi_AOT.csproj", "{79C7438E-72FC-4C43-B255-7B5D1AB94761}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,6 +88,10 @@ Global {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {53DBA160-9C57-4A01-A189-34FD4D052CD1}.Release|Any CPU.Build.0 = Release|Any CPU + {79C7438E-72FC-4C43-B255-7B5D1AB94761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79C7438E-72FC-4C43-B255-7B5D1AB94761}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79C7438E-72FC-4C43-B255-7B5D1AB94761}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79C7438E-72FC-4C43-B255-7B5D1AB94761}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,6 +107,7 @@ Global {D5C8BCC5-C997-475E-9E71-5BF807294BB6} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} {548DBE4A-0C06-4FB3-BEA6-6AB4A0386EEF} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} {53DBA160-9C57-4A01-A189-34FD4D052CD1} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} + {79C7438E-72FC-4C43-B255-7B5D1AB94761} = {CF13271D-BF3F-4167-BEBA-DD02D33992F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {70815049-1680-480A-BF5A-00536D6C9C20} @@ -110,6 +117,7 @@ Global ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{2705db4c-3bce-4cfc-9a30-b4bfd1f28c56}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{53dba160-9c57-4a01-a189-34fd4d052cd1}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{548dbe4a-0c06-4fb3-bea6-6ab4a0386eef}*SharedItemsImports = 5 + ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{79c7438e-72fc-4c43-b255-7b5d1ab94761}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{897e5c9c-8c0a-4fb6-960c-4d11aafd4491}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{9232da41-ca69-4fe3-b0c9-d8d85fec272a}*SharedItemsImports = 5 ..\samples\SampleWebApi.Shared\SampleWebApi.Shared.projitems*{b82830a0-fdfc-469d-b2a8-d657cd216451}*SharedItemsImports = 5 diff --git a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj index 99a5435..47386b0 100644 --- a/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj +++ b/src/AspNetCore.Authentication.Basic/AspNetCore.Authentication.Basic.csproj @@ -9,6 +9,10 @@ - net9.0 support added - Sample project for net9.0 added - Readme updated +- Nullable reference types enabled +- Language version set to latest +- Implicit usings enabled +- AOT support added Easy to use and very light weight Microsoft style Basic Scheme Authentication implementation for ASP.NET Core. Mihir Dilip @@ -21,6 +25,7 @@ latest enable enable + true true LICENSE.txt README.md diff --git a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs index b8ff529..bfa523e 100644 --- a/src/AspNetCore.Authentication.Basic/BasicExtensions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; namespace AspNetCore.Authentication.Basic { @@ -84,8 +85,13 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, /// /// /// The instance of +#if NET5_0_OR_GREATER + public static AuthenticationBuilder AddBasic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBasicUserValidationService>(this AuthenticationBuilder builder) where TBasicUserValidationService : class, IBasicUserValidationService + => builder.AddBasic(BasicDefaults.AuthenticationScheme); +#else public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(BasicDefaults.AuthenticationScheme); +#endif /// /// Adds basic authentication scheme to the project. It takes a implementation of as type parameter. @@ -95,8 +101,13 @@ public static AuthenticationBuilder AddBasic(this A /// /// The authentication scheme. /// The instance of +#if NET5_0_OR_GREATER + public static AuthenticationBuilder AddBasic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBasicUserValidationService>(this AuthenticationBuilder builder, string authenticationScheme) where TBasicUserValidationService : class, IBasicUserValidationService + => builder.AddBasic(authenticationScheme, configureOptions: null); +#else public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(authenticationScheme, configureOptions: null); +#endif /// /// Adds basic authentication scheme to the project. It takes a implementation of as type parameter. @@ -106,8 +117,13 @@ public static AuthenticationBuilder AddBasic(this A /// /// The . /// The instance of +#if NET5_0_OR_GREATER + public static AuthenticationBuilder AddBasic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBasicUserValidationService>(this AuthenticationBuilder builder, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService + => builder.AddBasic(BasicDefaults.AuthenticationScheme, configureOptions); +#else public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(BasicDefaults.AuthenticationScheme, configureOptions); +#endif /// /// Adds basic authentication scheme to the project. It takes a implementation of as type parameter. @@ -118,8 +134,13 @@ public static AuthenticationBuilder AddBasic(this A /// The authentication scheme. /// The . /// The instance of +#if NET5_0_OR_GREATER + public static AuthenticationBuilder AddBasic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBasicUserValidationService>(this AuthenticationBuilder builder, string authenticationScheme, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService + => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions); +#else public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions); +#endif /// /// Adds basic authentication scheme to the project. It takes a implementation of as type parameter. @@ -131,8 +152,13 @@ public static AuthenticationBuilder AddBasic(this A /// The display name. /// The . /// The instance of +#if NET5_0_OR_GREATER + public static AuthenticationBuilder AddBasic<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBasicUserValidationService>(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action? configureOptions) + where TBasicUserValidationService : class, IBasicUserValidationService +#else public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action? configureOptions) where TBasicUserValidationService : class, IBasicUserValidationService +#endif { // Adds implementation of IBasicUserValidationService to the dependency container. builder.Services.AddTransient(); diff --git a/src/AspNetCore.Authentication.Basic/BasicOptions.cs b/src/AspNetCore.Authentication.Basic/BasicOptions.cs index 6b63521..0bd7181 100644 --- a/src/AspNetCore.Authentication.Basic/BasicOptions.cs +++ b/src/AspNetCore.Authentication.Basic/BasicOptions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.AspNetCore.Authentication; +using System.Diagnostics.CodeAnalysis; namespace AspNetCore.Authentication.Basic { @@ -53,6 +54,9 @@ public BasicOptions() public bool IgnoreAuthenticationIfAllowAnonymous { get; set; } #endif +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif internal Type? BasicUserValidationServiceType { get; set; } = null; } } From 083c03d9565f2da1f3ec5c03662d155583a835b6 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Thu, 9 Jan 2025 22:32:35 +0000 Subject: [PATCH 7/8] fixed references in sample projects --- samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj | 8 ++++---- samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj b/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj index 257440e..ef3d195 100644 --- a/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj +++ b/samples/SampleWebApi_9_0/SampleWebApi_9_0.csproj @@ -8,12 +8,12 @@ - + - + diff --git a/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj b/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj index 233cc84..2a7380d 100644 --- a/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj +++ b/samples/SampleWebApi_AOT/SampleWebApi_AOT.csproj @@ -10,13 +10,13 @@ - + - + From 8a387ad2cc694907c36aed9dd3744a93ee540fa7 Mon Sep 17 00:00:00 2001 From: Mihir Dilip Date: Thu, 9 Jan 2025 22:36:59 +0000 Subject: [PATCH 8/8] codeql-analysis.yml updated --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 50f0d1c..dde5dcb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.9.0 with: - dotnet-version: 8.x.x + dotnet-version: 9.x.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL