From 8c257d072e1a792edbbf386f3eb4a67c00133e66 Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Fri, 2 Oct 2026 07:56:28 -0500 Subject: [PATCH 1/2] refactor(api-key): improve middleware security and dependency usage * Use a generic unauthorized message for missing and invalid API keys * Compare API keys using CryptographicOperations.FixedTimeEquals * Inject IEnvReader instead of creating it per request * Reuse the configured API key across requests --- src/Middlewares/ApiKeyMiddleware.cs | 32 ++++++++++++++++++----------- src/Program.cs | 1 + 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Middlewares/ApiKeyMiddleware.cs b/src/Middlewares/ApiKeyMiddleware.cs index 9f41a61..ef8b403 100644 --- a/src/Middlewares/ApiKeyMiddleware.cs +++ b/src/Middlewares/ApiKeyMiddleware.cs @@ -1,12 +1,17 @@ -using DotEnv.Core; +using System.Text; +using System.Security.Cryptography; using Microsoft.AspNetCore.Authorization; +using DotEnv.Core; using SimpleResults; -using System.Net; namespace Playtesters.API.Middlewares; -public class ApiKeyMiddleware(RequestDelegate next) +public class ApiKeyMiddleware( + IEnvReader envReader, + RequestDelegate next) { + private readonly byte[] _apiKeyBytes = Encoding.UTF8.GetBytes(envReader["API_KEY"]); + public async Task InvokeAsync(HttpContext context) { var endpoint = context.GetEndpoint(); @@ -18,22 +23,25 @@ public async Task InvokeAsync(HttpContext context) if (!context.Request.Headers.TryGetValue("X-Api-Key", out var providedKey)) { - var response = Result.Unauthorized("Missing API Key."); - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - await context.Response.WriteAsJsonAsync(response); + await Unauthorized(context); return; } - var envReader = new EnvReader(); - var apiKey = envReader["API_KEY"]; - if (!apiKey.Equals(providedKey)) + var providedKeyBytes = Encoding.UTF8.GetBytes(providedKey.ToString()); + + if (!CryptographicOperations.FixedTimeEquals(_apiKeyBytes, providedKeyBytes)) { - var response = Result.Unauthorized("Invalid API Key."); - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - await context.Response.WriteAsJsonAsync(response); + await Unauthorized(context); return; } await next(context); } + + private static async Task Unauthorized(HttpContext context) + { + Result result = Result.Unauthorized("Invalid API Key."); + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + await context.Response.WriteAsJsonAsync(result); + } } diff --git a/src/Program.cs b/src/Program.cs index e7180a2..1f48e72 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -16,6 +16,7 @@ builder.Services.AddSwaggerWithApiKey(); builder.Services.AddServices(); builder.Services.AddExceptionHandler(); +builder.Services.AddSingleton(new EnvReader(envVars)); builder.Services.AddDbContext(options => options.UseSqlite($"Data Source={dataSource}")); From ca86738c19b3928451fe314bc12645765793a684 Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Fri, 2 Oct 2026 08:09:33 -0500 Subject: [PATCH 2/2] fix: restore the messages from the original contract --- src/Middlewares/ApiKeyMiddleware.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Middlewares/ApiKeyMiddleware.cs b/src/Middlewares/ApiKeyMiddleware.cs index ef8b403..3e4c989 100644 --- a/src/Middlewares/ApiKeyMiddleware.cs +++ b/src/Middlewares/ApiKeyMiddleware.cs @@ -23,7 +23,7 @@ public async Task InvokeAsync(HttpContext context) if (!context.Request.Headers.TryGetValue("X-Api-Key", out var providedKey)) { - await Unauthorized(context); + await Unauthorized(context, "Missing API Key."); return; } @@ -31,16 +31,16 @@ public async Task InvokeAsync(HttpContext context) if (!CryptographicOperations.FixedTimeEquals(_apiKeyBytes, providedKeyBytes)) { - await Unauthorized(context); + await Unauthorized(context, "Invalid API Key."); return; } await next(context); } - private static async Task Unauthorized(HttpContext context) + private static async Task Unauthorized(HttpContext context, string message) { - Result result = Result.Unauthorized("Invalid API Key."); + Result result = Result.Unauthorized(message); context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsJsonAsync(result); }