using System; using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace AMWD.Common.AspNetCore.Security.BasicAuthentication { /// /// Implements a basic authentication. /// /// /// Initializes a new instance of the class. /// /// The following delegate in the process chain. /// A basic authentication validator. public class BasicAuthenticationMiddleware(RequestDelegate next, IBasicAuthenticationValidator validator) { private readonly RequestDelegate _next = next; private readonly IBasicAuthenticationValidator _validator = validator; /// /// The delegate invokation. /// Performs the authentication check. /// /// The corresponding HTTP context. /// An awaitable task. public async Task InvokeAsync(HttpContext httpContext) { #if NET8_0_OR_GREATER if (!httpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue)) { SetAuthenticateRequest(httpContext, _validator.Realm); return; } #else if (!httpContext.Request.Headers.ContainsKey("Authorization")) { SetAuthenticateRequest(httpContext, _validator.Realm); return; } #endif try { #if NET8_0_OR_GREATER var authHeader = AuthenticationHeaderValue.Parse(authHeaderValue); #else var authHeader = AuthenticationHeaderValue.Parse(httpContext.Request.Headers["Authorization"]); #endif byte[] decoded = Convert.FromBase64String(authHeader.Parameter); string plain = Encoding.UTF8.GetString(decoded); // See: https://www.rfc-editor.org/rfc/rfc2617, page 6 string username = plain.Split(':').First(); string password = plain[(username.Length + 1)..]; var principal = await _validator.ValidateAsync(username, password, httpContext.GetRemoteIpAddress(), httpContext.RequestAborted).ConfigureAwait(false); if (principal == null) { SetAuthenticateRequest(httpContext, _validator.Realm); return; } await _next.Invoke(httpContext).ConfigureAwait(false); } catch (Exception ex) { var logger = (ILogger)httpContext.RequestServices.GetService(typeof(ILogger)); logger?.LogError(ex, $"Falied to execute basic authentication middleware: {ex.InnerException?.Message ?? ex.Message}"); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; } } private static void SetAuthenticateRequest(HttpContext httpContext, string realm) { httpContext.Response.Headers.WWWAuthenticate = "Basic"; if (!string.IsNullOrWhiteSpace(realm)) httpContext.Response.Headers.WWWAuthenticate = $"Basic realm=\"{realm.Replace("\"", "")}\""; httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; } } }