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