diff --git a/src/presentation/DigitalData.TaskFlow.API/Middleware/ApiKeyAuthMiddleware.cs b/src/presentation/DigitalData.TaskFlow.API/Middleware/ApiKeyAuthMiddleware.cs
index 7653f9a..5384d2f 100644
--- a/src/presentation/DigitalData.TaskFlow.API/Middleware/ApiKeyAuthMiddleware.cs
+++ b/src/presentation/DigitalData.TaskFlow.API/Middleware/ApiKeyAuthMiddleware.cs
@@ -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;
///
/// Validates required API key header for API endpoints.
///
-public sealed class ApiKeyAuthMiddleware(RequestDelegate next)
+public sealed class ApiKeyAuthMiddleware(RequestDelegate next, IOptions apiKeyOptions)
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
+ private readonly string? _configuredApiKey = apiKeyOptions.Value.Key;
///
/// 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;
diff --git a/src/presentation/DigitalData.TaskFlow.API/Options/ApiKeyOptions.cs b/src/presentation/DigitalData.TaskFlow.API/Options/ApiKeyOptions.cs
new file mode 100644
index 0000000..0a61256
--- /dev/null
+++ b/src/presentation/DigitalData.TaskFlow.API/Options/ApiKeyOptions.cs
@@ -0,0 +1,8 @@
+namespace DigitalData.TaskFlow.API.Options;
+
+public sealed class ApiKeyAuthOptions
+{
+ public const string SectionName = "ApiKey";
+
+ public string? Key { get; init; }
+}
diff --git a/src/presentation/DigitalData.TaskFlow.API/Program.cs b/src/presentation/DigitalData.TaskFlow.API/Program.cs
index 6f4d115..eda400f 100644
--- a/src/presentation/DigitalData.TaskFlow.API/Program.cs
+++ b/src/presentation/DigitalData.TaskFlow.API/Program.cs
@@ -96,6 +96,10 @@ try
.AddOptions()
.Bind(config.GetSection(SwaggerOptions.SectionName));
+ builder.Services
+ .AddOptions()
+ .Bind(config.GetSection(ApiKeyAuthOptions.SectionName));
+
builder.Services.AddSerilogUi(options =>
{
options.UseSqliteServer(databaseOptions =>
@@ -110,11 +114,18 @@ try
var swaggerOptions = app.Services
.GetRequiredService>()
.Value;
+ var apiKeyOptions = app.Services
+ .GetRequiredService>()
+ .Value;
var enableSwagger = app.Environment.IsDevelopment() || swaggerOptions.EnabledInProduction;
+ var enforceApiKey = !string.IsNullOrWhiteSpace(apiKeyOptions.Key);
app.UseMiddleware();
- app.UseMiddleware();
+ if (enforceApiKey)
+ {
+ app.UseMiddleware();
+ }
app.UseSerilogRequestLogging();
if (enableSwagger)
diff --git a/src/presentation/DigitalData.TaskFlow.API/appsettings.Development.json b/src/presentation/DigitalData.TaskFlow.API/appsettings.Development.json
index cf2df3e..951d6c5 100644
--- a/src/presentation/DigitalData.TaskFlow.API/appsettings.Development.json
+++ b/src/presentation/DigitalData.TaskFlow.API/appsettings.Development.json
@@ -10,6 +10,9 @@
}
}
},
+ "ApiKey": {
+ "Key": null
+ },
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/src/presentation/DigitalData.TaskFlow.API/appsettings.json b/src/presentation/DigitalData.TaskFlow.API/appsettings.json
index a269b53..eb0f98a 100644
--- a/src/presentation/DigitalData.TaskFlow.API/appsettings.json
+++ b/src/presentation/DigitalData.TaskFlow.API/appsettings.json
@@ -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"
},