Add HTTP header authorization middleware

This commit is contained in:
2025-08-31 15:02:27 +02:00
parent 620bb57e6d
commit 89623f26db
5 changed files with 137 additions and 1 deletions

View File

@@ -0,0 +1,72 @@
using System.Net;
using Electricity.Api.Options;
namespace Electricity.Api;
internal class AllowedAccessMiddleware(
RequestDelegate next,
ILogger<AllowedAccessMiddleware> logger,
AllowedAccessOptions options
)
{
private readonly RequestDelegate next = next;
private readonly ILogger<AllowedAccessMiddleware> logger = logger;
private readonly AllowedAccessOptions options = options;
public async Task Invoke(HttpContext context)
{
if (
!context.Request.Headers.TryGetValue(
options.HttpIpAddressHeaderName,
out var headerValue
)
)
{
logger.LogDebug(
"Rejecting request missing the expected HTTP header {HeaderName}",
options.HttpIpAddressHeaderName
);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
if (headerValue.Count != 1)
{
logger.LogDebug(
"Rejecting request with malformed HTTP header {HeaderName}",
options.HttpIpAddressHeaderName
);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
if (string.IsNullOrWhiteSpace(headerValue[0]))
{
logger.LogDebug(
"Rejecting request with malformed value {HeaderValue} in HTTP header {HeaderName}",
headerValue[0],
options.HttpIpAddressHeaderName
);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
if (!options.HttpIpAddressHeaderValues.Contains(headerValue[0]))
{
logger.LogInformation(
"Rejecting request with disallowed header value {HeaderValue} in HTTP header {HeaderName}",
headerValue[0],
options.HttpIpAddressHeaderName
);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
logger.LogDebug(
"Accepting request with allowed value {HeaderValue} in HTTP header {HeaderName}",
headerValue[0],
options.HttpIpAddressHeaderName
);
await next.Invoke(context);
}
}

View File

@@ -0,0 +1,47 @@
namespace Electricity.Api.Options;
internal class AllowedAccessOptions(IConfiguration configuration)
{
public string HttpIpAddressHeaderName { get; } = ResolveHttpIpAddressHeaderName(configuration);
public string[] HttpIpAddressHeaderValues { get; } =
ResolveHttpIpAddressHeaderValues(configuration);
private static string ResolveHttpIpAddressHeaderName(IConfiguration configuration)
{
const string configurationKey = "AllowedAccess:HttpIpAddressHeaderName";
var value = configuration["AllowedAccess:HttpIpAddressHeaderName"];
if (string.IsNullOrWhiteSpace(value))
{
throw new NotSupportedException($"The app setting {configurationKey} cannot be empty");
}
return value;
}
private static string[] ResolveHttpIpAddressHeaderValues(IConfiguration configuration)
{
const string configurationKey = "AllowedAccess:HttpIpAddressHeaderValues";
var values = configuration.GetSection(configurationKey).Get<List<string>>();
if (values == null || values.Count == 0)
{
throw new NotSupportedException(
$"The app setting {configurationKey} must be an string array with at least 1 value"
);
}
List<string> validatedValues = [];
foreach (var value in values)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NotSupportedException(
$"The value \"{value}\" from app setting {configurationKey} cannot be empty"
);
}
validatedValues.Add(value);
}
return validatedValues.ToArray();
}
}

View File

@@ -1,5 +1,6 @@
using System.CommandLine; using System.CommandLine;
using Electricity.Api; using Electricity.Api;
using Electricity.Api.Options;
using Electricity.Api.Services; using Electricity.Api.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -67,6 +68,11 @@ internal class Program
config.JsonSerializerOptions.WriteIndented = true; config.JsonSerializerOptions.WriteIndented = true;
}); });
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddSingleton(sp => new AllowedAccessOptions(
sp.GetRequiredService<IConfiguration>()
));
builder.Services.AddScoped<ElectricityService>(); builder.Services.AddScoped<ElectricityService>();
var app = builder.Build(); var app = builder.Build();
@@ -79,6 +85,9 @@ internal class Program
app.UseCors("default"); app.UseCors("default");
} }
app.UseMiddleware<AllowedAccessMiddleware>(
app.Services.GetRequiredService<AllowedAccessOptions>()
);
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();

View File

@@ -1,7 +1,11 @@
{ {
"AllowedAccess": {
"HttpIpAddressHeaderName": "Host",
"HttpIpAddressHeaderValues": ["localhost:7290"]
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Trace",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
} }

View File

@@ -1,5 +1,9 @@
{ {
"AllowedHosts": "*", "AllowedHosts": "*",
"AllowedAccess": {
"HttpIpAddressHeaderName": "X-Real-IP",
"HttpIpAddressHeaderValues": ["213.233.220.64", "86.83.136.215"]
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",