Basic Authentication in ASP.NET Core

By FoxLearn 2/17/2025 9:47:22 AM   32
Authentication and Authorization are critical aspects of securing any web application. ASP.NET Core Web API offers various methods to handle these tasks, with Basic Authentication being one of the simpler and widely used methods.

What is Basic Authentication?

To fully understand Basic Authentication, it’s important to know the difference between Authentication and Authorization:

  • Authentication verifies the identity of a user (typically by username and password).
  • Authorization ensures that an authenticated user has the necessary permissions to access certain resources.

Basic Authentication involves sending the user's credentials (username and password) encoded in Base64 format in the HTTP header. The server then decodes the credentials and validates them against stored values.

How Does Basic Authentication Work?

Let’s break down how Basic Authentication operates:

  1. The client sends a request to the server without any credentials.
  2. The server responds with a 401 Unauthorized status code and indicates that Basic Authentication is required.
  3. The client sends another request, this time with the credentials encoded in the Authorization header.
  4. The server decodes the credentials, validates them, and if they match, it responds with a 200 OK status and the requested data.

Advantages of Basic Authentication

  • Simplicity: Basic Authentication is easy to understand and implement.
  • Universal Support: Most HTTP clients and browsers support it.
  • Stateless: It doesn’t require session storage, making it scalable.

Implementing Basic Authentication in ASP.NET Core Web API

We will now go through the steps to create a simple Web API and implement Basic Authentication.

Step 1: Create a Class Library Project (Customer.Domain)

Start by creating a class library project named Customer.Domain.

Step 2: Add a Customer Model

In the Models folder, create a Customer.cs class:

namespace Customer.Domain.Models
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}

Step 3: Create Customer.Application Layer

Now, create a layer called Customer.Application to handle the business logic. Inside this layer, create Repository and Services folders.

Step 4: Implement Repositories and Services

In the Repository folder, create an interface ICustomerRepository.cs:

namespace Customer.Application.Repository
{
    public interface ICustomerRepository
    {
        Task<Customer.Domain.Models.Customer?> ValidateCustomer(string username, string password);
        Task<List<Customer.Domain.Models.Customer>> GetAllCustomers();
        Task<Customer.Domain.Models.Customer> GetCustomerById(int id);
    }
}

In the Services folder, create an interface ICustomerService.cs:

namespace Customer.Application.Services
{
    public interface ICustomerService
    {
        Task<Customer.Domain.Models.Customer?> ValidateCustomer(string username, string password);
        Task<List<Customer.Domain.Models.Customer>> GetAllCustomers();
        Task<Customer.Domain.Models.Customer> GetCustomerById(int id);
    }
}

Next, implement the service:

namespace Customer.Application.Services
{
    public class CustomerService : ICustomerService
    {
        private readonly ICustomerRepository _customerRepository;

        public CustomerService(ICustomerRepository customerRepository)
        {
            _customerRepository = customerRepository;
        }

        public Task<List<Customer.Domain.Models.Customer>> GetAllCustomers() => _customerRepository.GetAllCustomers();
        
        public Task<Customer.Domain.Models.Customer> GetCustomerById(int id) => _customerRepository.GetCustomerById(id);
        
        public Task<Customer.Domain.Models.Customer?> ValidateCustomer(string username, string password) => 
            _customerRepository.ValidateCustomer(username, password);
    }
}

Step 5: Add References to Projects

Add references to Customer.Domain in Customer.Application.

Step 6: Create Customer.Infrastructure Layer

Create a new layer called Customer.Infrastructure to interact with the data. This layer will contain the repository for fetching customer data.

Step 7: Implement Repository

Inside the Customer.Infrastructure.Repository folder, create the CustomerRepository.cs file:

namespace Customer.Infrastructure.Repository
{
    public class CustomerRepository : ICustomerRepository
    {
        List<Customer.Domain.Models.Customer> customers = new List<Customer.Domain.Models.Customer>
        {
            new Customer.Domain.Models.Customer { Id = 1, Name = "John Doe", Email = "[email protected]", UserName = "john", Password = "Password123" },
            new Customer.Domain.Models.Customer { Id = 2, Name = "Jane Smith", Email = "[email protected]", UserName = "jane", Password = "Password456" }
        };

        public async Task<List<Customer.Domain.Models.Customer>> GetAllCustomers() => customers.ToList();
        
        public async Task<Customer.Domain.Models.Customer> GetCustomerById(int id) => customers.FirstOrDefault(c => c.Id == id);
        
        public async Task<Customer.Domain.Models.Customer?> ValidateCustomer(string username, string password) =>
            customers.FirstOrDefault(c => c.UserName == username && c.Password == password);
    }
}

Step 8: Create Web API Project

Create a new ASP.NET Core Web API project called CustomerApi. Add references to Customer.Domain, Customer.Application, and Customer.Infrastructure.

Step 9: Implement Basic Authentication Handler

In the CustomerApi project, create a BasicAuthenticationHandler.cs to handle authentication:

using Customer.Application.Repository;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;

namespace CustomerApi.Authentication
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly ICustomerRepository _customerRepository;

        public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> optionsMonitor,
            ILoggerFactory loggerFactory,
            ICustomerRepository customerRepository) : base(optionsMonitor, loggerFactory)
        {
            _customerRepository = customerRepository;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
            var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationHeader.Parameter)).Split(':', 2);

            if (credentials.Length != 2)
                return AuthenticateResult.Fail("Invalid Header Content");

            var user = await _customerRepository.ValidateCustomer(credentials[0], credentials[1]);

            if (user == null)
                return AuthenticateResult.Fail("Invalid Customer");

            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Name),
            };

            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }
}

Step 10: Register the Authentication Handler

In Program.cs, register the BasicAuthenticationHandler and services:

builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", options => { });

builder.Services.AddSingleton<ICustomerRepository, CustomerRepository>();
builder.Services.AddSingleton<ICustomerService, CustomerService>();

Step 11: Create Customer Controller

Now, create the CustomerController.cs to manage customer-related API endpoints:

using Microsoft.AspNetCore.Mvc;

namespace CustomerApi.Controllers
{
    [Authorize(AuthenticationSchemes = "BasicAuthentication")]
    [ApiController]
    [Route("[controller]")]
    public class CustomerController : ControllerBase
    {
        private readonly ICustomerService _customerService;

        public CustomerController(ICustomerService customerService)
        {
            _customerService = customerService;
        }

        [HttpGet]
        public async Task<ActionResult<List<Customer.Domain.Models.Customer>>> GetCustomers() =>
            await _customerService.GetAllCustomers();
    }
}

Step 12: Run and Test the Application

Run the application and use Postman to send requests. If the credentials are missing or incorrect, the server will respond with 401 Unauthorized. Upon providing valid credentials, the server will return 200 OK with the requested data.

You have successfully implemented Basic Authentication in an ASP.NET Core Web API. This method provides an easy and straightforward way to authenticate users. In future articles, we will explore more advanced authentication mechanisms, such as token-based authentication or role-based access control.