using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace EmbedIO.Authentication
{
///
/// Implements HTTP basic authentication.
///
public abstract class BasicAuthenticationModuleBase : WebModuleBase
{
private readonly string _wwwAuthenticateHeaderValue;
///
/// Initializes a new instance of the class.
///
/// The base URL path.
/// The authentication realm.
///
/// If is or the empty string,
/// the property will be set equal to
/// BaseRoute.
///
protected BasicAuthenticationModuleBase(string baseRoute, string? realm)
: base(baseRoute)
{
Realm = string.IsNullOrEmpty(realm) ? BaseRoute : realm;
_wwwAuthenticateHeaderValue = $"Basic realm=\"{Realm}\" charset=UTF-8";
}
///
public sealed override bool IsFinalHandler => false;
///
/// Gets the authentication realm.
///
public string Realm { get; }
///
protected sealed override async Task OnRequestAsync(IHttpContext context)
{
async Task IsAuthenticatedAsync()
{
try
{
var (userName, password) = GetCredentials(context.Request);
return await VerifyCredentialsAsync(context.RequestedPath, userName, password, context.CancellationToken)
.ConfigureAwait(false);
}
catch (FormatException)
{
// Credentials were not formatted correctly.
return false;
}
}
context.Response.Headers.Set(HttpHeaderNames.WWWAuthenticate, _wwwAuthenticateHeaderValue);
if (!await IsAuthenticatedAsync().ConfigureAwait(false))
throw HttpException.Unauthorized();
}
///
/// Verifies the credentials given in the Authentication request header.
///
/// The URL path requested by the client. Note that this is relative
/// to the module's BaseRoute.
/// The user name, or if none has been given.
/// The password, or if none has been given.
/// A use to cancel the operation.
/// A whose result will be if the given credentials
/// are valid, if they are not.
protected abstract Task VerifyCredentialsAsync(string path, string userName, string password, CancellationToken cancellationToken);
private static (string UserName, string Password) GetCredentials(IHttpRequest request)
{
var authHeader = request.Headers[HttpHeaderNames.Authorization];
if (authHeader == null)
return default;
if (!authHeader.StartsWith("basic ", StringComparison.OrdinalIgnoreCase))
return default;
string credentials;
try
{
credentials = WebServer.DefaultEncoding.GetString(Convert.FromBase64String(authHeader.Substring(6).Trim()));
}
catch (FormatException)
{
return default;
}
var separatorPos = credentials.IndexOf(':');
return separatorPos < 0
? (credentials, string.Empty)
: (credentials.Substring(0, separatorPos), credentials.Substring(separatorPos + 1));
}
}
}