Skip to content

Commit 6a2a31a

Browse files
committed
Add keyed providers, logging, and update targets
1 parent 0c87213 commit 6a2a31a

13 files changed

Lines changed: 156 additions & 84 deletions

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<PackageVersion Include="AssemblyMetadata.Generators" Version="2.1.0" />
88
<PackageVersion Include="Bogus" Version="35.6.5" />
99
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
10-
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
10+
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
1111
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
1212
<PackageVersion Include="MinVer" Version="7.0.0" />
13-
<PackageVersion Include="Scalar.AspNetCore" Version="2.12.47" />
13+
<PackageVersion Include="Scalar.AspNetCore" Version="2.12.48" />
1414
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.4" />
1515
<PackageVersion Include="xunit" Version="2.9.3" />
1616
<PackageVersion Include="XUnit.Hosting" Version="4.0.0" />

samples/Sample.Middleware/Program.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,13 @@ public static void Main(string[] args)
3030
app.UseAuthorization();
3131

3232
app.MapGet("/weather", () => WeatherFaker.Instance.Generate(5))
33-
.WithName("GetWeatherForecast")
34-
.WithOpenApi();
33+
.WithName("GetWeatherForecast");
3534

3635
app.MapGet("/users", () => UserFaker.Instance.Generate(10))
37-
.WithName("GetUsers")
38-
.WithOpenApi();
36+
.WithName("GetUsers");
3937

4038
app.MapGet("/addresses", () => AddressFaker.Instance.Generate(10))
41-
.WithName("GetAddresses")
42-
.WithOpenApi();
39+
.WithName("GetAddresses");
4340

4441
app.Run();
4542
}

src/AspNetCore.SecurityKey/AspNetCore.SecurityKey.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
</PropertyGroup>

src/AspNetCore.SecurityKey/AuthenticationBuilderExtensions.cs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.AspNetCore.Authentication;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.DependencyInjection.Extensions;
34

45
namespace AspNetCore.SecurityKey;
56

@@ -27,7 +28,9 @@ public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder bu
2728
/// <returns>
2829
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
2930
/// </returns>
30-
public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder builder, string authenticationScheme)
31+
public static AuthenticationBuilder AddSecurityKey(
32+
this AuthenticationBuilder builder,
33+
string authenticationScheme)
3134
=> builder.AddSecurityKey(authenticationScheme: authenticationScheme, displayName: null, configureOptions: null);
3235

3336
/// <summary>
@@ -39,7 +42,9 @@ public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder bu
3942
/// <returns>
4043
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
4144
/// </returns>
42-
public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder builder, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
45+
public static AuthenticationBuilder AddSecurityKey(
46+
this AuthenticationBuilder builder,
47+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
4348
=> builder.AddSecurityKey(authenticationScheme: SecurityKeyAuthenticationDefaults.AuthenticationScheme, displayName: null, configureOptions: configureOptions);
4449

4550
/// <summary>
@@ -51,7 +56,10 @@ public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder bu
5156
/// <returns>
5257
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
5358
/// </returns>
54-
public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder builder, string authenticationScheme, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
59+
public static AuthenticationBuilder AddSecurityKey(
60+
this AuthenticationBuilder builder,
61+
string authenticationScheme,
62+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
5563
=> builder.AddSecurityKey(authenticationScheme: authenticationScheme, displayName: null, configureOptions: configureOptions);
5664

5765
/// <summary>
@@ -67,7 +75,11 @@ public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder bu
6775
/// <exception cref="ArgumentNullException">
6876
/// Thrown when <paramref name="builder"/> is <c>null</c>.
6977
/// </exception>
70-
public static AuthenticationBuilder AddSecurityKey(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
78+
public static AuthenticationBuilder AddSecurityKey(
79+
this AuthenticationBuilder builder,
80+
string authenticationScheme,
81+
string? displayName,
82+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
7183
=> builder.AddSecurityKey<SecurityKeyValidator, SecurityKeyExtractor>(authenticationScheme, displayName, configureOptions);
7284

7385
/// <summary>
@@ -91,7 +103,9 @@ public static AuthenticationBuilder AddSecurityKey<TValidator>(this Authenticati
91103
/// <returns>
92104
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
93105
/// </returns>
94-
public static AuthenticationBuilder AddSecurityKey<TValidator>(this AuthenticationBuilder builder, string authenticationScheme)
106+
public static AuthenticationBuilder AddSecurityKey<TValidator>(
107+
this AuthenticationBuilder builder,
108+
string authenticationScheme)
95109
where TValidator : class, ISecurityKeyValidator
96110
=> builder.AddSecurityKey<TValidator, SecurityKeyExtractor>(authenticationScheme: authenticationScheme, displayName: null, configureOptions: null);
97111

@@ -104,7 +118,9 @@ public static AuthenticationBuilder AddSecurityKey<TValidator>(this Authenticati
104118
/// <returns>
105119
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
106120
/// </returns>
107-
public static AuthenticationBuilder AddSecurityKey<TValidator>(this AuthenticationBuilder builder, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
121+
public static AuthenticationBuilder AddSecurityKey<TValidator>(
122+
this AuthenticationBuilder builder,
123+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
108124
where TValidator : class, ISecurityKeyValidator
109125
=> builder.AddSecurityKey<TValidator, SecurityKeyExtractor>(authenticationScheme: SecurityKeyAuthenticationDefaults.AuthenticationScheme, displayName: null, configureOptions: configureOptions);
110126

@@ -132,7 +148,9 @@ public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this
132148
/// <returns>
133149
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
134150
/// </returns>
135-
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this AuthenticationBuilder builder, string authenticationScheme)
151+
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(
152+
this AuthenticationBuilder builder,
153+
string authenticationScheme)
136154
where TValidator : class, ISecurityKeyValidator
137155
where TExtractor : class, ISecurityKeyExtractor
138156
=> builder.AddSecurityKey<TValidator, TExtractor>(authenticationScheme: authenticationScheme, displayName: null, configureOptions: null);
@@ -147,7 +165,9 @@ public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this
147165
/// <returns>
148166
/// The <see cref="AuthenticationBuilder"/> instance for chaining further authentication configuration.
149167
/// </returns>
150-
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this AuthenticationBuilder builder, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
168+
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(
169+
this AuthenticationBuilder builder,
170+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
151171
where TValidator : class, ISecurityKeyValidator
152172
where TExtractor : class, ISecurityKeyExtractor
153173
=> builder.AddSecurityKey<TValidator, TExtractor>(authenticationScheme: SecurityKeyAuthenticationDefaults.AuthenticationScheme, displayName: null, configureOptions: configureOptions);
@@ -167,15 +187,29 @@ public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this
167187
/// <exception cref="ArgumentNullException">
168188
/// Thrown when <paramref name="builder"/> is <c>null</c>.
169189
/// </exception>
170-
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
190+
public static AuthenticationBuilder AddSecurityKey<TValidator, TExtractor>(
191+
this AuthenticationBuilder builder,
192+
string authenticationScheme,
193+
string? displayName,
194+
Action<SecurityKeyAuthenticationSchemeOptions>? configureOptions)
171195
where TValidator : class, ISecurityKeyValidator
172196
where TExtractor : class, ISecurityKeyExtractor
173197
{
174198
ArgumentNullException.ThrowIfNull(builder);
175199

176-
builder.Services.AddOptions<SecurityKeyAuthenticationSchemeOptions>(authenticationScheme);
200+
// Register default services
177201
builder.Services.AddSecurityKey<TValidator, TExtractor>();
178202

203+
// Add the authentication scheme options to the service collection to ensure they are available for post-configuration
204+
builder.Services.AddOptions<SecurityKeyAuthenticationSchemeOptions>(authenticationScheme);
205+
206+
// Register the keyed provider services for the specified authentication scheme
207+
builder.Services.TryAddKeyedScoped<ISecurityKeyValidator, TValidator>(authenticationScheme);
208+
builder.Services.TryAddKeyedScoped<ISecurityKeyExtractor, TExtractor>(authenticationScheme);
209+
210+
// Post-configure the authentication scheme options to set the ProviderServiceKey after the keyed provider is registered
211+
builder.Services.PostConfigure<SecurityKeyAuthenticationSchemeOptions>(authenticationScheme, options => options.ProviderServiceKey = authenticationScheme);
212+
179213
return builder.AddScheme<SecurityKeyAuthenticationSchemeOptions, SecurityKeyAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
180214
}
181215
}

src/AspNetCore.SecurityKey/DependencyInjectionExtensions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.DependencyInjection.Extensions;
23
using Microsoft.Extensions.Logging;
34

45
namespace AspNetCore.SecurityKey;
@@ -59,11 +60,11 @@ public static IServiceCollection AddSecurityKey<TValidator, TExtractor>(this ISe
5960
if (configure != null)
6061
services.Configure(configure);
6162

62-
services.AddSingleton<ISecurityKeyExtractor, TExtractor>();
63-
services.AddSingleton<ISecurityKeyValidator, TValidator>();
63+
services.TryAddSingleton<ISecurityKeyExtractor, TExtractor>();
64+
services.TryAddSingleton<ISecurityKeyValidator, TValidator>();
6465

6566
// used by SecurityKeyAttribute
66-
services.AddSingleton<SecurityKeyAuthorizationFilter>();
67+
services.TryAddSingleton<SecurityKeyAuthorizationFilter>();
6768

6869
return services;
6970
}

src/AspNetCore.SecurityKey/ISecurityKeyValidator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public interface ISecurityKeyValidator
2525
/// </summary>
2626
/// <param name="value">The security API key to authenticate. May be <c>null</c> if not provided in the request.</param>
2727
/// <param name="ipAddress">The IP address of the client making the request.</param>
28+
/// <param name="scheme">The authentication scheme to use for the <see cref="ClaimsIdentity"/>. If <see langword="null"/>, the default scheme is used.</param>
2829
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
2930
/// <returns>
3031
/// A <see cref="ValueTask{ClaimsIdentity}"/> representing the result of the authentication.
3132
/// The returned <see cref="ClaimsIdentity"/> should reflect the authenticated principal if the key is valid.
3233
/// </returns>
33-
ValueTask<ClaimsIdentity> Authenticate(string? value, IPAddress? ipAddress = null, CancellationToken cancellationToken = default);
34+
ValueTask<ClaimsIdentity> Authenticate(string? value, IPAddress? ipAddress = null, string? scheme = null, CancellationToken cancellationToken = default);
3435
}

src/AspNetCore.SecurityKey/SecurityKeyAuthenticationHandler.cs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Text.Encodings.Web;
33

44
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Logging;
67
using Microsoft.Extensions.Options;
78

@@ -13,32 +14,20 @@ namespace AspNetCore.SecurityKey;
1314
/// </summary>
1415
public class SecurityKeyAuthenticationHandler : AuthenticationHandler<SecurityKeyAuthenticationSchemeOptions>
1516
{
16-
private readonly ISecurityKeyExtractor _securityKeyExtractor;
17-
private readonly ISecurityKeyValidator _securityKeyValidator;
17+
private static readonly AuthenticateResult InvalidSecurityKey = AuthenticateResult.Fail("Invalid Security Key");
1818

19-
#pragma warning disable CS0618 // allow ISystemClock for compatibility
2019
/// <summary>
2120
/// Initializes a new instance of the <see cref="SecurityKeyAuthenticationHandler"/> class.
2221
/// </summary>
2322
/// <param name="options">The options monitor for <see cref="SecurityKeyAuthenticationSchemeOptions"/>.</param>
2423
/// <param name="logger">The factory for creating <see cref="ILogger"/> instances.</param>
2524
/// <param name="encoder">The <see cref="UrlEncoder"/> for encoding URLs.</param>
26-
/// <param name="clock">The <see cref="ISystemClock"/> for time-based operations.</param>
27-
/// <param name="securityKeyExtractor">The service used to extract the security API key from the HTTP context.</param>
28-
/// <param name="securityKeyValidator">The service used to validate and authenticate the security API key.</param>
2925
public SecurityKeyAuthenticationHandler(
3026
IOptionsMonitor<SecurityKeyAuthenticationSchemeOptions> options,
3127
ILoggerFactory logger,
32-
UrlEncoder encoder,
33-
ISystemClock clock,
34-
ISecurityKeyExtractor securityKeyExtractor,
35-
ISecurityKeyValidator securityKeyValidator)
36-
: base(options, logger, encoder, clock)
37-
{
38-
_securityKeyExtractor = securityKeyExtractor;
39-
_securityKeyValidator = securityKeyValidator;
40-
}
41-
#pragma warning restore CS0618
28+
UrlEncoder encoder)
29+
: base(options, logger, encoder)
30+
{ }
4231

4332
/// <summary>
4433
/// Handles authentication for the current request by extracting and validating the security API key.
@@ -49,17 +38,32 @@ public SecurityKeyAuthenticationHandler(
4938
/// </returns>
5039
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
5140
{
52-
var securityKey = _securityKeyExtractor.GetKey(Context);
41+
// Resolve provider when configured; otherwise use the default registration.
42+
var keyExtractor = string.IsNullOrEmpty(Options.ProviderServiceKey)
43+
? Context.RequestServices.GetRequiredService<ISecurityKeyExtractor>()
44+
: Context.RequestServices.GetRequiredKeyedService<ISecurityKeyExtractor>(Options.ProviderServiceKey);
45+
46+
// Extract the security key from the request using the configured extractor
47+
var securityKey = keyExtractor.GetKey(Context);
5348

5449
// If no security key is provided, return no result
5550
if (string.IsNullOrEmpty(securityKey))
5651
return AuthenticateResult.NoResult();
5752

58-
var ipAddress = _securityKeyExtractor.GetRemoteAddress(Context);
53+
var ipAddress = keyExtractor.GetRemoteAddress(Context);
54+
55+
// Resolve provider when configured; otherwise use the default registration.
56+
var keyValidator = string.IsNullOrEmpty(Options.ProviderServiceKey)
57+
? Context.RequestServices.GetRequiredService<ISecurityKeyValidator>()
58+
: Context.RequestServices.GetRequiredKeyedService<ISecurityKeyValidator>(Options.ProviderServiceKey);
5959

60-
var identity = await _securityKeyValidator.Authenticate(securityKey, ipAddress);
60+
// Authenticate the security key and get the claims identity
61+
var identity = await keyValidator.Authenticate(securityKey, ipAddress, Scheme.Name, Context.RequestAborted);
6162
if (!identity.IsAuthenticated)
62-
return AuthenticateResult.Fail("Invalid Security Key");
63+
{
64+
Logger.LogWarning("Invalid security key {SecurityKey} from IP {IPAddress}", securityKey, ipAddress);
65+
return InvalidSecurityKey;
66+
}
6367

6468
// create a user claim for the security key
6569
var principal = new ClaimsPrincipal(identity);

src/AspNetCore.SecurityKey/SecurityKeyAuthenticationSchemeOptions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ namespace AspNetCore.SecurityKey;
1010
/// <seealso cref="Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions" />
1111
public class SecurityKeyAuthenticationSchemeOptions : AuthenticationSchemeOptions
1212
{
13-
13+
/// <summary>
14+
/// The service key used to resolve a custom <see cref="ISecurityKeyExtractor"/> and <see cref="ISecurityKeyValidator"/> from the
15+
/// dependency injection container. When set, the keyed service registered under this key is used instead of the default provider.
16+
/// </summary>
17+
public string? ProviderServiceKey { get; set; }
1418
}

src/AspNetCore.SecurityKey/SecurityKeyEndpointFilter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public SecurityKeyEndpointFilter(
5656
if (await _securityKeyValidator.Validate(securityKey, ipAddress))
5757
return await next(context);
5858

59+
_logger.LogWarning("Invalid security key {SecurityKey} from IP {IPAddress}", securityKey, ipAddress);
5960
return Results.Unauthorized();
6061
}
6162
}

src/AspNetCore.SecurityKey/SecurityKeyMiddleware.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public async Task InvokeAsync(HttpContext context)
6262
return;
6363
}
6464

65+
_logger.LogWarning("Invalid security key {SecurityKey} from IP {IPAddress}", securityKey, ipAddress);
6566
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
6667
}
6768
}

0 commit comments

Comments
 (0)