Add API key authentication middleware support
Introduced `ApiKeyAuthOptions` for API key configuration, including a `Key` property and `SectionName` for binding. Updated `ApiKeyAuthMiddleware` to validate the `X-Api-Key` header against the configured API key. Added conditional middleware application in `Program.cs` based on the presence of a configured API key. Bound the `ApiKey` section in `appsettings.json` and `appsettings.Development.json` to store the API key, with a default value of `null`. This improves security and centralizes API key management.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using DigitalData.TaskFlow.API.Contracts.Common;
|
||||
using DigitalData.TaskFlow.API.Options;
|
||||
using DigitalData.TaskFlow.Domain.Common;
|
||||
using DigitalData.TaskFlow.Domain.Enums;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DigitalData.TaskFlow.API.Middleware;
|
||||
@@ -8,9 +10,10 @@ namespace DigitalData.TaskFlow.API.Middleware;
|
||||
/// <summary>
|
||||
/// Validates required API key header for API endpoints.
|
||||
/// </summary>
|
||||
public sealed class ApiKeyAuthMiddleware(RequestDelegate next)
|
||||
public sealed class ApiKeyAuthMiddleware(RequestDelegate next, IOptions<ApiKeyAuthOptions> apiKeyOptions)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private readonly string? _configuredApiKey = apiKeyOptions.Value.Key;
|
||||
|
||||
/// <summary>
|
||||
/// Executes middleware logic.
|
||||
@@ -25,7 +28,7 @@ public sealed class ApiKeyAuthMiddleware(RequestDelegate next)
|
||||
}
|
||||
|
||||
if (context.Request.Headers.TryGetValue("X-Api-Key", out var values)
|
||||
&& !string.IsNullOrWhiteSpace(values.FirstOrDefault()))
|
||||
&& string.Equals(values.FirstOrDefault(), _configuredApiKey, StringComparison.Ordinal))
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DigitalData.TaskFlow.API.Options;
|
||||
|
||||
public sealed class ApiKeyAuthOptions
|
||||
{
|
||||
public const string SectionName = "ApiKey";
|
||||
|
||||
public string? Key { get; init; }
|
||||
}
|
||||
@@ -96,6 +96,10 @@ try
|
||||
.AddOptions<SwaggerOptions>()
|
||||
.Bind(config.GetSection(SwaggerOptions.SectionName));
|
||||
|
||||
builder.Services
|
||||
.AddOptions<ApiKeyAuthOptions>()
|
||||
.Bind(config.GetSection(ApiKeyAuthOptions.SectionName));
|
||||
|
||||
builder.Services.AddSerilogUi(options =>
|
||||
{
|
||||
options.UseSqliteServer(databaseOptions =>
|
||||
@@ -110,11 +114,18 @@ try
|
||||
var swaggerOptions = app.Services
|
||||
.GetRequiredService<Microsoft.Extensions.Options.IOptions<SwaggerOptions>>()
|
||||
.Value;
|
||||
var apiKeyOptions = app.Services
|
||||
.GetRequiredService<Microsoft.Extensions.Options.IOptions<ApiKeyAuthOptions>>()
|
||||
.Value;
|
||||
|
||||
var enableSwagger = app.Environment.IsDevelopment() || swaggerOptions.EnabledInProduction;
|
||||
var enforceApiKey = !string.IsNullOrWhiteSpace(apiKeyOptions.Key);
|
||||
|
||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||
app.UseMiddleware<ApiKeyAuthMiddleware>();
|
||||
if (enforceApiKey)
|
||||
{
|
||||
app.UseMiddleware<ApiKeyAuthMiddleware>();
|
||||
}
|
||||
app.UseSerilogRequestLogging();
|
||||
|
||||
if (enableSwagger)
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiKey": {
|
||||
"Key": null
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
"ConnectionStrings": {
|
||||
"TaskFlowDb": "Server=SDD-VHP04-SQL19\\DD_TESTING01;Database=DD_ECM;User Id=sa;Password=123456789dD!;Encrypt=false;TrustServerCertificate=True;"
|
||||
},
|
||||
"ApiKey": {
|
||||
"Key": null
|
||||
},
|
||||
"MediatR": {
|
||||
"LuckyPennySoftLicenseKey": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxODE2MTI4MDAwIiwiaWF0IjoiMTc4NDYyNDU1NyIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiMDE5ODNjNTllNGIzNzI4ZWJmZDMxMDNjMmE0ODZlODUiLCJzdWJfaWQiOiItIiwiZWRpdGlvbiI6IjAiLCJ0eXBlIjoiMiJ9.IUUO926m9crYGYxMjjKD_n9BnUm-EDyjFIn0YmMUCo7C-QTwvB8WhXP8veTSFsBq-leIIDJ4jyl7Pgc_7ciwg1XhUSIs4mkQroEUaSFCGOxw7Pi41WM8MK5YFSaqLTYYXec9zxgiJbGzABbh3CHTSup3okGnVm_CMoPEs91l2c0A6N1JyZy74urd_tF0KGVKf0MOvzdlQIWLQ8o73S4pTv2N-F6UlzI0fdMtTHMLNNQyr0NdWdnuBk_jMBXO-gy5RE_oCRfMTTYRX2n3XLK6pTfXE0Ct338o9F5sH8Ph2lTXSu56cpdsfZOQZGqCH0LoFp1Dd7RJgIgNmBiTGfvDnA"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user