using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; public sealed class GatewayApiKeyAuthenticationHandler : AuthenticationHandler { public const string SchemeName = "GatewayApiKey"; private readonly IConfiguration _config; public GatewayApiKeyAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IConfiguration config) : base(options, logger, encoder) => _config = config; protected override Task HandleAuthenticateAsync() { if (!Request.Headers.TryGetValue("X-Api-Key", out var suppliedHeader)) return Task.FromResult(AuthenticateResult.NoResult()); var configured = _config["ApiKey:Gateway"]; if (string.IsNullOrEmpty(configured)) return Task.FromResult(AuthenticateResult.Fail("Gateway API key not configured.")); if (!FixedTimeEquals(suppliedHeader.ToString(), configured)) return Task.FromResult(AuthenticateResult.Fail("Invalid API key.")); var claims = new List { new("client_type", "gateway") }; if (Request.Headers.TryGetValue("X-Gateway-Id", out var gatewayIdHeader) && Guid.TryParse(gatewayIdHeader.ToString(), out _)) claims.Add(new Claim("gateway_id", gatewayIdHeader.ToString()!)); var identity = new ClaimsIdentity(claims, SchemeName); var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), SchemeName); return Task.FromResult(AuthenticateResult.Success(ticket)); } private static bool FixedTimeEquals(string supplied, string configured) { var suppliedBytes = Encoding.UTF8.GetBytes(supplied); var configuredBytes = Encoding.UTF8.GetBytes(configured); return CryptographicOperations.FixedTimeEquals(suppliedBytes, configuredBytes); } }