1
0
Files
common/AMWD.Common.AspNetCore/Security/BasicAuthentication/BasicAuthenticationHandler.cs
2024-01-14 13:10:33 +01:00

87 lines
3.8 KiB
C#

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
{
#if NET8_0_OR_GREATER
/// <summary>
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="BasicAuthenticationHandler"/> class.
/// </remarks>
/// <param name="options" > The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="validator">An basic autentication validator implementation.</param>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, IBasicAuthenticationValidator validator)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
#else
/// <summary>
/// Implements the <see cref="AuthenticationHandler{TOptions}"/> for Basic Authentication.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="BasicAuthenticationHandler"/> class.
/// </remarks>
/// <param name="options" > The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
/// <param name="validator">An basic autentication validator implementation.</param>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public class BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IBasicAuthenticationValidator validator)
: AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder, clock)
#endif
{
private readonly ILogger _logger = logger.CreateLogger<BasicAuthenticationHandler>();
private readonly IBasicAuthenticationValidator _validator = validator;
/// <inheritdoc/>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var endpoint = Context.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != 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);
}
}
}