using System; using System.Linq; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace AMWD.Common.AspNetCore.Security.BasicAuthentication { /// /// Implements the for Basic Authentication. /// [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class BasicAuthenticationHandler : AuthenticationHandler { private readonly ILogger _logger; private readonly IBasicAuthenticationValidator _validator; #if NET8_0_OR_GREATER /// /// Initializes a new instance of the class. /// /// The monitor for the options instance. /// The . /// The . /// An basic autentication validator implementation. public BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, IBasicAuthenticationValidator validator) : base(options, logger, encoder) { _logger = logger.CreateLogger(); _validator = validator; } #endif #if NET6_0 /// /// Initializes a new instance of the class. /// /// The monitor for the options instance. /// The . /// The . /// The . /// An basic autentication validator implementation. public BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IBasicAuthenticationValidator validator) : base(options, logger, encoder, clock) { _logger = logger.CreateLogger(); _validator = validator; } #endif /// protected override async Task HandleAuthenticateAsync() { var endpoint = Context.GetEndpoint(); if (endpoint?.Metadata?.GetMetadata() != null) return AuthenticateResult.NoResult(); if (!Request.Headers.TryGetValue("Authorization", out var authHeaderValue)) return AuthenticateResult.Fail("Authorization header missing"); ClaimsPrincipal principal; try { var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue); string plain = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter)); // See: https://www.rfc-editor.org/rfc/rfc2617, page 6 string username = plain.Split(':').First(); string password = plain[(username.Length + 1)..]; var ipAddress = Context.GetRemoteIpAddress(); principal = await _validator.ValidateAsync(username, password, ipAddress, Context.RequestAborted).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, $"Handling the Basic Authentication failed: {ex.Message}"); return AuthenticateResult.Fail("Authorization header invalid"); } if (principal == null) return AuthenticateResult.Fail("Invalid credentials"); var ticket = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket); } } }