using System.Text.Encodings.Web; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Authentication; namespace Journal.WebGateway.Security; public class ApiKeyAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IConfiguration config) : AuthenticationHandler(options, logger, encoder) { protected override Task HandleAuthenticateAsync() { if (!Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey)) return Task.FromResult(AuthenticateResult.NoResult()); // Fallback to other schemes (like Cookie) var expectedApiKey = config.GetValue("Security:ApiKey"); if (string.Equals(expectedApiKey, extractedApiKey, StringComparison.Ordinal)) { var claims = new[] { new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "ApiKeyUser") }; var identity = new System.Security.Claims.ClaimsIdentity(claims, Scheme.Name); var principal = new System.Security.Claims.ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); return Task.FromResult(AuthenticateResult.Success(ticket)); } return Task.FromResult(AuthenticateResult.Fail("Invalid API Key provided.")); } // Do NOT issue a 401 challenge from this handler. // Let the Cookie scheme handle it (which redirects to /gateway/login). protected override Task HandleChallengeAsync(AuthenticationProperties properties) { return Task.CompletedTask; } }