Monday, April 17, 2023

Add basic authentication to ASP.Net Core Web API

Basic authentication is probably simplest authentication type for Web API when HTTP requests are authenticated using username and passwords provided in HTTP request headers. In this post I will describe how to add basic authentication to ASP.Net Core Web API.

At first we need to add reference to idunno.Authentication.Basic nuget package. It contains infrastructure for basic authentication ready to be used in ASP.Net Core/.NET Core projects. Also we will need simple validation service which will check provided username/password and based on that will authenticate/reject requests:

public interface IBasicAuthValidationService
{
    bool AreCredentialsValid(string username, string password);
}

public class BasicAuthValidationService : IBasicAuthValidationService
{
    private string username;
    private string password;

    public BasicAuthValidationService(string username, string password)
    {
        this.username = username;
        this.password = password;
    }

    public bool AreCredentialsValid(string username, string password)
    {
        return string.Compare(this.username, username, true) == 0 && this.password == password;
    }
}

In our example BasicAuthValidationService simply compares credentials coming from HTTP request with predefined allowed username/password.

Then in Web API's Program.cs we need to configure basic authentication itself. Since username and password are sent in HTTP request headers it is important to force using HTTPS for securing communication with our API. We will do that by adding RequireHttpsAttribute filter and UseHttpsRedirection middleware:

builder.Services.AddScoped<IBasicAuthValidationService>(c =>
{
    // get allowed username and password to authenticate requests
    string username = ...;
    string password = ...;
    return new BasicAuthValidationService(uername, password);
});

// add basic auth
builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
    .AddBasic(options =>
    {
        options.Realm = "Test.Api";
        options.Events = new BasicAuthenticationEvents
        {
            OnValidateCredentials = context =>
            {
                var validationService = context.HttpContext.RequestServices.GetService<IBasicAuthValidationService>();
                if (validationService.AreCredentialsValid(context.Username, context.Password))
                {
                    var claims = new[]
                    {
                        new Claim(ClaimTypes.NameIdentifier, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer),
                        new Claim(ClaimTypes.Name, context.Username, ClaimValueTypes.String, context.Options.ClaimsIssuer)
                    };

                    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
                    context.Success();
                }

                return Task.CompletedTask;
            }
        };
    });

builder.Services.Configure<MvcOptions>(options =>
{
    options.Filters.Add(new RequireHttpsAttribute());
});

var app = builder.Build();
... app.UseHttpsRedirection();

Our Web API is now ready to be used with basic authentication. If you use Swagger for generating client for Web API (see Generate strongly-typed C# client for ASP.Net Core Web API with OpenAPI (swagger) support running on localhost) then you may configure your Web API client for basic authentication like this:

services.AddHttpClient<HttpClient>();
services.AddSingleton<IApiClient>(c =>
{
    string baseUrl = ...;
    string username = ...;
    string password = ...;
    var httpClientFactory = c.GetRequiredService<IHttpClientFactory>();
    var httpClient = httpClientFactory.CreateClient();
    httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(username, password);
    return new ApiClient(baseUrl, httpClient);
});

Now both server and client sides support basic authentication.

No comments:

Post a Comment