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);
}
}
}