Basic Authentication in ASP.NET Core Web API

How to Implement Basic Authentication in ASP.NET Core Web API

In this article, I will discuss how to implement Basic Authentication in ASP.NET Core Web API with an example. Please read our previous article discussing RSA Asymmetric Encryption in ASP.NET Core Web API with an Example. As part of this article, we will discuss the following pointers.

  1. Why do we need Authentication in Web API?
  2. What is Basic Authentication in ASP.NET Core Web API?
  3. How does Basic Authentication Work in ASP.NET Core Web API?
  4. Implementing Basic Authentication in ASP.NET Core Web API Application
  5. Understanding Claims, ClaimsIdentity, ClaimsPrincipal, AuthenticationTicket
  6. Advantages and Disadvantages of Using Basic Authentication in ASP.NET Core Web API
  7. When to Use Basic Authentication?
Why do we need Authentication in Web API?

Let’s start the discussion with one of the rest constraints, i.e., stateless constraints. The Stateless Constraint states that the communication between the client and server must be stateless between the requests. That means we should not store the client information on the server that is required to process the request. The request that is coming from the client should contain all the necessary information that is required by the server to process the request. This ensures that each request coming from the client can be treated independently by the server.

The above Stateless approach is fine, and the advantage is that we can separate the client and server at any given point in time without affecting others. Here, the client can use any application, such as desktop, mobile, or web. These client applications can be developed using any programming language like Java, PHP, C#, Android, iOS, Angular, React, etc. The server does not remember the client once the request has been processed, So each and every request coming from the client is new to the server, and the server needs to check the request (most of the time, the HTTP header) to identify the user. 

So, to process the request by the server, the client needs to pass its credentials with each request. Then, the server will check and match the credentials with any persistent storage (most of the time, it may be a database). If the credentials are found in the persistent storage, the server will treat that HTTP request as a valid request and process it; otherwise, it simply returns an unauthorized error to the client.

Authentication and Authorization can be implemented in many ways in an ASP.NET Core Web API Application. In this article, I will discuss how to implement Basic Authentication.

What is Basic Authentication in ASP.NET Core Web API?

In ASP.NET Core Web API, Basic Authentication is a way to secure API Endpoints by requiring users to provide credentials (username and password) that are then verified against a database or other storage system.

In Basic Authentication, the client passes their username and password in the HTTP request header. Typically, using this technique, we encrypt user credentials into a base64-encoded string and decrypt this base64-encoded string into plain text.

How does Basic Authentication Work in ASP.NET Core Web API?

Basic Authentication is a straightforward method for securing Web APIs. It involves sending a username and password with each HTTP request, making it suitable for simple scenarios where high security is not the primary concern. To better understand how Basic Authentication Works in ASP.NET Core Web API, please have a look at the following diagram.

How does Basic Authentication Work in ASP.NET Core Web API?

Understanding the Flow of Basic Authentication:
  • Credentials Transmission: When a client makes a request to an API Endpoint secured with Basic Authentication, it must include the username and password in the request header. These credentials are combined into a single string, “username:password” and then encoded using Base64.
  • Authorization Header: The encoded string is then transmitted in the HTTP header with the prefix “Basic” such as Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l (where the encoded string is a Base64 representation of “username:password”).
  • Server-Side Verification: Upon receiving the request, the server decodes the Base64 string to retrieve the username and password. It then validates these credentials against a stored database. If the credentials are valid, the request is allowed to proceed. If not, the server rejects the request, typically with a 401 Unauthorized status code.

In Basic Authentication, if the client didn’t send the credentials in the request header, the server will return 401 (Unauthorized). The response will also include a WWW-Authenticate header, indicating that the server supports Basic Authentication and that you can see in the diagram for the first request that the authorization header is not included.

Implementing Basic Authentication in ASP.NET Core Web API Application:

Implementing Basic Authentication in an ASP.NET Core Web API Application involves creating a custom authentication scheme that checks the Authorization header of incoming requests for a Base64-encoded username and password.

In this demo, we are going to do the following things to implement and understand the Basic Authentication in ASP.NET Core Web API Application:

  • Server Application: We will create an ASP.NET Core Web API Project that will act as the server application. This application will validate the user with Basic Authentication.
  • Create the User Model: This model will represent the user data.
  • Create the User Repository: This repository will have hardcoded users and a method to validate users.
  • Setup Basic Authentication: We will configure the authentication scheme in the application.
  • Create an API Controller: This controller will handle CRUD operations for the user model with hardcoded data.
  • Client Application: We will create a Dot Net Console Application as the Client Application that will consume the Web API services with Basic Authentication.
Creating Server Application

Create a new ASP.NET Core Web API Project with the name BasicAuthenticationDemo. This will be our Server application, i.e., we will develop restful services that will be consumed by the client application. Here, we will implement Basic Authentication to validate the user. Within the Project root directory, create a folder named Models.

Create the User Model:

Next, create a class file named User.cs within the Models folder and copy and paste the following code. This will be the model that holds the user data.

namespace BasicAuthenticationDemo.Models
{
    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        // In a real-world application, never store plain passwords
        public string Password { get; set; } 
    }
}
Create the User Repository:

Next, create a class file named UserRepository.cs within the Models folder and copy and paste the following code. This will be the repository where we perform the typical database CRUD Operations on the User entity. Here, we have hardcoded the data. The following code is self-explained, so please go through the comment lines for a better understanding:

namespace BasicAuthenticationDemo.Models
{
    namespace BasicAuthenticationDemo.Models 
    {
        // Interface defining the contract for user repository operations.
        public interface IUserRepository
        {
            Task<User?> ValidateUser(string username, string password); // Method to validate a user's credentials.
            Task<List<User>> GetAllUsers(); // Method to retrieve all users.
            Task<User?> GetUserById(int id); // Method to fetch a single user by ID.
            Task<User> AddUser(User user); // Method to add a new user.
            Task<User> UpdateUser(User user); // Method to update an existing user's details.
            Task DeleteUser(int id); // Method to delete a user by ID.
        }

        // Concrete implementation of IUserRepository.
        public class UserRepository : IUserRepository
        {
            // In-memory list of users.
            private List<User> users = new List<User>
            {
                // Initial set of users. Use hashed passwords in a production environment.
                new User { Id = 1, Username = "admin", Password = "admin" },
                new User { Id = 2, Username = "user", Password = "user" },
                new User { Id = 3, Username = "Pranaya", Password = "Test@1234" },
                new User { Id = 4, Username = "Kumar", Password = "Admin@123" }
            };

            // Validates user credentials against the stored list.
            public async Task<User?> ValidateUser(string username, string password)
            {
                await Task.Delay(100); // Simulates a delay, mimicking database latency.
                return users.FirstOrDefault(u => u.Username == username && u.Password == password); // Returns the user if credentials match.
            }

            // Retrieves all users.
            public async Task<List<User>> GetAllUsers()
            {
                await Task.Delay(100); // Simulates a delay.
                return users.ToList(); // Converts the list of users to a new list and returns it.
            }

            // Retrieves a user by ID.
            public async Task<User?> GetUserById(int id)
            {
                await Task.Delay(100); // Simulates a delay.
                return users.FirstOrDefault(u => u.Id == id); // Returns the user if found.
            }

            // Adds a new user if no duplicate ID is found.
            public async Task<User> AddUser(User user)
            {
                await Task.Delay(100); // Simulates a delay.
                if (users.Any(u => u.Id == user.Id))
                {
                    throw new Exception("User already exists with the given ID."); // Exception if user with same ID exists.
                }

                users.Add(user); // Adds the new user to the list.
                return user; // Returns the added user.
            }

            // Updates an existing user's details.
            public async Task<User> UpdateUser(User user)
            {
                await Task.Delay(100); // Simulates a delay.
                var existingUser = await GetUserById(user.Id); // Fetches the user by ID.
                if (existingUser == null)
                {
                    throw new Exception("User not found."); // Throws an exception if user not found.
                }

                existingUser.Username = user.Username; // Updates username.
                existingUser.Password = user.Password; // Updates password, consider hashing in production.
                return existingUser; // Returns the updated user.
            }

            // Deletes a user by ID.
            public async Task DeleteUser(int id)
            {
                await Task.Delay(100); // Simulates a delay.
                var user = await GetUserById(id); // Fetches the user by ID.
                if (user == null)
                {
                    throw new Exception("User not found."); // Throws an exception if user not found.
                }

                users.Remove(user); // Removes the user from the list.
            }
        }
    }
}
Register the User Repository:

Next, we need to register the User Repository service to the built-in dependency injection container. So, add the following line for code to the Program class.

builder.Services.AddSingleton<IUserRepository, UserRepository>();

Creating Basic Authentication Handler Service:

Next, we need to implement the Custom Basic Authentication Handler service. So, create a class file named BasicAuthenticationHandler.cs within the Models folder and copy and paste the following code. The following code is self-explained, so please go through the comment lines for a better understanding:

using BasicAuthenticationDemo.Models.BasicAuthenticationDemo.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace BasicAuthenticationDemo.Models
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserRepository _userRepository; // Dependency injection for the user repository

        // Constructor
        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options, // Options for configuring authentication schemes
            ILoggerFactory logger, // Factory to create logger objects
            UrlEncoder encoder, // Encoder for URL to ensure safe URLs
            IUserRepository userRepository) // User repository to handle user data
            : base(options, logger, encoder) // Pass options, logger, and encoder to the base class
        {
            _userRepository = userRepository; // Initialize user repository with dependency injection
        }

        // Method to handle the authentication process
        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            // Check if the Authorization header is present
            if (!Request.Headers.ContainsKey("Authorization"))
            {
                // Fail authentication if header is missing
                return AuthenticateResult.Fail("Missing Authorization Header"); 
            }
            
            User? user;
            try
            {
                // Parse the Authorization header and validate its format
                if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out var authHeader))
                {
                    // Fail authentication if header format is wrong
                    return AuthenticateResult.Fail("Invalid Authorization Header Format");
                }
                
                // Decode the Base64 encoded credentials from the header
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter ?? string.Empty);

                // Split decoded credentials into username and password
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':', 2);

                if (credentials.Length != 2)
                {
                    // Ensure credentials are in correct format
                    return AuthenticateResult.Fail("Invalid Authorization Header Content");
                }

                // Extract username
                var username = credentials[0];

                // Extract password
                var password = credentials[1]; 

                // Validate user against the stored credentials
                user = await _userRepository.ValidateUser(username, password);
            }
            catch (FormatException) // Handle format exceptions for Base64 decoding
            {
                return AuthenticateResult.Fail("Invalid Base64 Encoding in Authorization Header");
            }
            catch (Exception) // Handle general exceptions
            {
                return AuthenticateResult.Fail("Error Processing Authorization Header");
            }

            if (user == null)
            {
                // Check if user is not found or invalid credentials
                return AuthenticateResult.Fail("Invalid Username or Password");
            }

            // Create claims based on the valid user
            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };

            // Create an identity with claims
            var identity = new ClaimsIdentity(claims, Scheme.Name);

            // Create principal from identity
            var principal = new ClaimsPrincipal(identity);

            // Create ticket with principal and scheme
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            // Return success with the authentication ticket
            return AuthenticateResult.Success(ticket); 
        }

        // Method to handle the authentication challenge
        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            // Set the WWW-Authenticate header in the response. This instructs the client
            // (usually a web browser) to prompt the user with a login dialog for username and password.
            // "Basic realm=\"BasicAuthenticationDemo\"" describes the authentication method (Basic)
            // and the realm. The realm can be used to describe the protected area or to prompt
            // the user with a more specific identifier about what they are accessing.
            // "charset=\"UTF-8\"" ensures that the characters entered by the user are encoded correctly.
            Response.Headers["WWW-Authenticate"] = "Basic realm=\"BasicAuthenticationDemo\", charset=\"UTF-8\"";

            // Set the HTTP status code to 401 Unauthorized to indicate that the request has failed
            // authentication and needs proper credentials to proceed.
            Response.StatusCode = 401;

            // Send a custom message in the response body. This message can be seen by the client if they
            // access the raw HTTP response. It's a clear indicator as to why the request was rejected.
            await Response.WriteAsync("You need to authenticate to access this resource.");
        }
    }
}
Understanding the Role of Constructor:
  • The constructor of the BasicAuthenticationHandler class initializes the handler with the necessary components and services for authentication.
  • It accepts dependencies such as IOptionsMonitor<AuthenticationSchemeOptions>, ILoggerFactory, UrlEncoder, and IUserRepository. These dependencies are used throughout the class for configuring the authentication scheme, logging, URL encoding, and user validation, respectively.
  • It initializes the base class AuthenticationHandler with the provided options, logger and encoder. This setup is important for using the ASP.NET Core’s built-in functionalities for handling authentication.
Understanding HandleAuthenticateAsync Method:

This method is used to implement the core functionality of authenticating requests using basic authentication. It does so by performing the following actions:

  • Header Validation: Check if the Authorization header is present and correctly formatted.
  • Credential Extraction and Decoding: Extracts and decodes the Base64 encoded credentials from the Authorization header.
  • User Validation: Validates the decoded username and password against the user repository.
  • Claim Creation: If the user is validated successfully, it creates claims based on the user’s information.
  • Principal and Ticket Generation: Constructs a ClaimsPrincipal from the claims and generates an AuthenticationTicket, which is used to establish the user’s identity.
  • Result Handling: If the user is authenticated successfully, it returns a success result with the authentication ticket; otherwise, it returns a failure result with an appropriate error message.
HandleChallengeAsync Method

This method is triggered when the ASP.NET Core authentication middleware determines that authentication is required but has not been provided or the provided authentication is invalid.

  • Setting WWW-Authenticate Header: This header is important in the Basic Authentication flow. It informs the client that authentication is required and specifies the authentication scheme (Basic) and the realm (BasicAuthenticationDemo). The realm describes the secured area or endpoint.
  • Response Code: The HTTP 401 status code explicitly tells the client that their request was unauthorized due to a lack of valid credentials.
  • Character Encoding: Specifying charset=”UTF-8″ is important for internationalization, ensuring user input is correctly encoded, especially if usernames or passwords include non-ASCII characters.
Understanding Claims, ClaimsIdentity, ClaimsPrincipal, AuthenticationTicket:

In the context of Authentication and Authorization in .NET Applications, especially when using ASP.NET Core, the concepts of Claims, ClaimsIdentity, ClaimsPrincipal, and AuthenticationTicket play important roles. Let us understand these objects in detail and try to understand the Roles and Responsibilities of these objects in ASP.NET Core Web API Authentication and Authorization:

Claims

A claim is a statement about a user, typically used for identification. Claims can contain information like user ID, user name, email, roles, and more. Each claim consists of a type (indicating the kind of information, e.g., ClaimTypes.Name) and a value (the actual information, e.g., the user’s name).

ClaimsIdentity

A ClaimsIdentity object represents a single identity authenticated by the system. It is a collection of claims attached to a user, along with an authentication type (specifying how the identity was authenticated). For example, in a basic authentication scenario, we might set this authentication type to “Basic”. This object doesn’t just hold the user’s claims but also manages the identity’s authentication state.

ClaimsPrincipal

A ClaimsPrincipal is a security principal in the .NET security system that holds one or more instances of ClaimsIdentity. It represents the application user at runtime and can contain multiple identities (reflecting different ways the user has been authenticated). This allows for scenarios where an identity might be partially authenticated through one method (e.g., password) and fully authenticated through another (e.g., two-factor authentication).

AuthenticationTicket

In ASP.NET Core, an authentication ticket stores the user’s identity after they have been authenticated. It contains the ClaimsPrincipal representing the user, along with additional metadata like the authentication scheme used. This ticket represents the user’s authenticated session and can be checked by the application or middleware in subsequent requests to verify that the user is authenticated and retrieve identity information.

Basic Authentication Scheme Setup:

In the Program.cs class file, configure the Authentication Scheme as Basic. So, please add the following code to the Program class. The following code configures the Basic Authentication using a custom authentication handler.

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

This line begins configuring the authentication services within the ASP.NET Core dependency injection system. The string “BasicAuthentication” specifies the application’s default authentication scheme. This is useful when multiple schemes are present, and you want to set one as the application’s default. When a specific scheme isn’t specified in an authorization request, this default scheme will be used.

AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(“BasicAuthentication”, options => { })

This method is used to add and configure a specific authentication scheme. Here, it adds a scheme named “BasicAuthentication”. This method takes the following two Generic Parameters:

  • AuthenticationSchemeOptions: This is the type parameter specifying the options class used for the scheme. AuthenticationSchemeOptions is often used as a base class for more specific options, but here, it’s used directly, indicating no additional configuration options are being specified beyond the defaults.
  • BasicAuthenticationHandler: This is the type of authentication handler that will process authentication requests for this scheme. The handler is responsible for the logic of authenticating incoming requests, typically by parsing the Authorization header, validating credentials, and issuing a principal if the authentication is successful.
Ignoring Camel Case Naming Convention:

We want the JSON property name to be displayed as it is. So, modify the AddControllers service as follows in the Program.cs class file:

builder.Services.AddControllers()
.AddJsonOptions(options =>
{
    // This will use the property names as defined in the C# model
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
Create an API Controller:

Next, create an API Empty Controller named UsersController within the Controller folder and copy and paste the following code. The following code is self-explained, so please go through the comment lines for a better understanding:

using BasicAuthenticationDemo.Models;
using BasicAuthenticationDemo.Models.BasicAuthenticationDemo.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace BasicAuthenticationDemo.Controllers
{
    // Applies authorization to ensure all actions within the controller require authentication.
    [Authorize(AuthenticationSchemes = "BasicAuthentication")]
    //[Authorize]

    // Marks the class as a web API controller with additional features enabled by ASP.NET Core.
    [ApiController]

    // Defines the route template using the controller name.
    [Route("api/[controller]")] 
    public class UsersController : ControllerBase
    {
        // Repository interface for user operations, injected via DI.
        private readonly IUserRepository _userRepository; 
        
        // Constructor accepting IUserRepository via dependency injection.
        public UsersController(IUserRepository userRepository)
        {
            // Assigns the injected repository to a private field.
            _userRepository = userRepository; 
        }

        // GET: api/users
        [HttpGet] // Marks this method as responding to HTTP GET requests.
        public async Task<ActionResult<IEnumerable<User>>> GetUsers()
        {
            // Retrieves all users from the repository.
            var users = await _userRepository.GetAllUsers();

            // Returns the list of users with an HTTP 200 status code.
            return Ok(users); 
        }

        // GET api/users/{id}
        [HttpGet("{id}")] // Marks this method as responding to HTTP GET with a route parameter "id".
        public async Task<ActionResult<User>> GetUser(int id)
        {
            var user = await _userRepository.GetUserById(id); // Retrieves a user by their ID.
            if (user == null) // Checks if the user is not found.
            {
                return NotFound("User not found."); // Returns a 404 status code if no user is found.
            }
            return Ok(user); // Returns the found user with an HTTP 200 status code.
        }

        // POST api/users
        [HttpPost] // Marks this method as responding to HTTP POST requests.
        public async Task<ActionResult<User>> CreateUser([FromBody] User user)
        {
            try
            {
                var createdUser = await _userRepository.AddUser(user); // Tries to add a new user to the repository.
                // Returns a 201 status code and the route to access the newly created user.
                return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, createdUser);
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message); // Returns a 400 status code if there's an exception, with the exception message.
            }
        }

        // PUT api/users/{id}
        [HttpPut("{id}")] // Marks this method as responding to HTTP PUT requests, with a route parameter "id".
        public async Task<ActionResult> UpdateUser(int id, [FromBody] User user)
        {
            if (id != user.Id) // Checks if the URL id matches the user id in the body.
            {
                return BadRequest("ID mismatch in the URL and body."); // Returns a 400 status if they don't match.
            }

            try
            {
                await _userRepository.UpdateUser(user); // Tries to update the user in the repository.
                return NoContent(); // Returns a 204 status code indicating that the operation was successful but no content is returned.
            }
            catch (Exception ex)
            {
                if (ex.Message == "User not found.")
                {
                    return NotFound(ex.Message); // Returns a 404 status code if the user is not found.
                }
                return BadRequest(ex.Message); // Returns a 400 status code for other exceptions.
            }
        }

        // DELETE api/users/{id}
        [HttpDelete("{id}")] // Marks this method as responding to HTTP DELETE requests, with a route parameter "id".
        public async Task<ActionResult> DeleteUser(int id)
        {
            try
            {
                await _userRepository.DeleteUser(id); // Tries to delete the user by ID.
                return NoContent(); // Returns a 204 status code indicating the user was successfully deleted.
            }
            catch (Exception ex)
            {
                if (ex.Message == "User not found.")
                {
                    return NotFound(ex.Message); // Returns a 404 status code if the user is not found.
                }
                return BadRequest(ex.Message); // Returns a 400 status code for other exceptions.
            }
        }
    }
}
Creating Client Application:

Now, we will create a Dot Net Console Application named APIConsumerApp that will consume the Web API services with Basic Authentication. Once you create the Console Application, modify the Program class as follows. The following code demonstrates a typical use case of HttpClient to interact with a REST API, including full CRUD operations and handling HTTP request headers for Basic Authentication.

using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;

namespace ApiConsumerApp
{
    class Program
    {
        // Initialize a static HttpClient instance for the lifetime of the application.
        static HttpClient client = new HttpClient();

        static async Task Main(string[] args)
        {
            // Set the base address of the API.
            client.BaseAddress = new Uri("https://localhost:7215");
            
            // Clear any previously set request headers.
            client.DefaultRequestHeaders.Accept.Clear();
            
            // Add JSON to the Accept header to tell the server to send data in JSON format.
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Set up basic authentication for all requests.
            var byteArray = Encoding.ASCII.GetBytes("admin:admin"); // Encoding the username and password as ASCII.
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

            try
            {
                // Perform a GET request to retrieve all users.
                Console.WriteLine("Getting all users...");
                var users = await GetAsync("api/users");
                Console.WriteLine(users);

                // Perform a POST request to create a new user.
                Console.WriteLine("Creating a new user...");
                var newUser = new { Id = 5, Username = "newuser", Password = "newpassword" };
                var postResult = await PostAsync("api/users", newUser);
                Console.WriteLine(postResult);

                // Perform a PUT request to update a user.
                Console.WriteLine("Updating a user...");
                var updatedUser = new { Id = 5, Username = "updateduser", Password = "updatedpassword" };
                var putResult = await PutAsync("api/users/5", updatedUser);
                Console.WriteLine(putResult);

                // Perform a DELETE request to remove a user.
                Console.WriteLine("Deleting a user...");
                var deleteResult = await DeleteAsync("api/users/5");
                Console.WriteLine(deleteResult);

                // Wait for a key press before closing to see the results.
                Console.ReadKey();
            }
            catch (Exception e)
            {
                // Output any exceptions to the console.
                Console.WriteLine(e.Message);
            }
        }

        // Method for GET requests.
        static async Task<string> GetAsync(string path)
        {
            HttpResponseMessage response = await client.GetAsync(path);
            // Check if the request was successful and read the response content as a string.
            return response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : $"Error: {response.StatusCode}";
        }

        // Method for POST requests.
        static async Task<string> PostAsync(string path, object value)
        {
            HttpResponseMessage response = await client.PostAsJsonAsync(path, value);
            // Check if the request was successful and read the response content as a string.
            return response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : $"Error: {response.StatusCode}";
        }

        // Method for PUT requests.
        static async Task<string> PutAsync(string path, object value)
        {
            HttpResponseMessage response = await client.PutAsJsonAsync(path, value);
            // Return a success message or an error message depending on the request status.
            return response.IsSuccessStatusCode ? "Updated successfully." : $"Error: {response.StatusCode}";
        }

        // Method for DELETE requests.
        static async Task<string> DeleteAsync(string path)
        {
            HttpResponseMessage response = await client.DeleteAsync(path);
            // Return a success message or an error message depending on the request status.
            return response.IsSuccessStatusCode ? "Deleted successfully." : $"Error: {response.StatusCode}";
        }
    }
}
Understanding the Code:
  • HttpClient is used to send HTTP requests and receive HTTP responses from a resource identified by a URI.
  • The example uses HTTP Methods such as GET to fetch data, POST to create data, PUT to update data, and DELETE to remove data.
  • Basic authentication is used here, where the username and password are encoded and sent in the header.
  • After each call, it checks if the operation was successful and prints the result accordingly. If not successful, it prints the error status code.

Now, run the application, and you should see the following result:

ASP.NET Core Web API Basic Authentication with an Example

Generating the Base64 Encode String Online:

It is also possible to generate the Base64 Encode String Online. Just visit the following URL.

https://www.base64encode.org/

Enter the username and password separated by a colon (:) in the Encode to Base64 format textbox, and then click on the Encode button to generate the Base64-encoded value.

Advantages and Disadvantages of Using Basic Authentication in ASP.NET Core Web API

Basic Authentication is a straightforward method for securing Web APIs, where clients provide a username and password in the request header. This method is widely supported and easy to implement. The following are some of the advantages and disadvantages of using Basic Authentication in ASP.NET Core Web API:

Advantages of Using Basic Authentication in ASP.NET Core Web API
  • Simplicity: Basic Authentication is simple to implement and easy to understand. The client sends a username and password with each request, which the server decodes and validates. This straightforwardness makes it easy to debug and maintain.
  • Universal Support: Almost all HTTP clients and browsers support Basic Authentication out of the box, making it a default choice for various types of applications and services.
  • Stateless: Each request is independent as it contains all the necessary data for authentication. This statelessness is ideal for RESTful APIs since it does not require server-side sessions.
Disadvantages of Using Basic Authentication in ASP.NET Core Web API
  • Base64 Encoding Is Not Encryption: Basic Authentication uses Base64 encoding, which is easily decoded. This encoding does not provide security by itself; credentials can be extracted if intercepted. To secure the credentials, transmission over HTTPS is necessary.
  • Credential Exposure: Since credentials are sent with each request, the risk of exposure is significantly higher compared to token-based methods, where credentials are exchanged using a token in subsequent requests.
  • Performance Overhead: Sending credentials with every request increases the size of the request headers, thereby adding overhead to each HTTP transaction.
  • No Additional Metadata or Expiry: Unlike token-based authentication (e.g., JWT), Basic Authentication does not inherently carry or support metadata such as token expiry, user roles, or other attributes unless these are separately managed on the server.
When to Use Basic Authentication?

Small, Internal, or Simple Applications: It is often used for simple, internal applications where security requirements are not that important or where interactions occur between trusted systems over secure networks.

In the next article, I will discuss how to implement Role-Based Basic Authentication in ASP.NET Core Web API Application. In this article, I explain the ASP.NET Core Web API Basic Authentication step by step with an example. I hope you enjoy this ASP.NET Core Web API Basic Authentication article.

1 thought on “Basic Authentication in ASP.NET Core Web API”

  1. blank

    Thank you, overall a good tutorial except for some issues:
    The repository is inside the namespace BasicAuthenticationDemo.Models twice.
    This results in the other classes having to have the following using statement:
    using BasicAuthenticationDemo.Models.BasicAuthenticationDemo.Models;
    Is this intended?
    Is it required by ASP.NET Core Web API?

    Also the portnumbers of the API and the console application are different which results in them not connecting properly.
    After setting the same port on both applications they still do not connect because the console application throws the following exception:
    System.Net.Http.HttpRequestException
    HResult=0x80131501
    Message=The SSL connection could not be established, see inner exception.
    Source=System.Net.Http
    StackTrace:
    at System.Net.Http.ConnectHelper.d__2.MoveNext()
    at System.Threading.Tasks.ValueTask`1.get_Result()
    at System.Net.Http.HttpConnectionPool.d__103.MoveNext()
    at System.Threading.Tasks.ValueTask`1.get_Result()
    at System.Net.Http.HttpConnectionPool.d__105.MoveNext()
    at System.Threading.Tasks.ValueTask`1.get_Result()
    at System.Net.Http.HttpConnectionPool.d__79.MoveNext()
    at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.d__1.MoveNext()
    at System.Threading.Tasks.ValueTask`1.get_Result()
    at System.Net.Http.HttpConnectionPool.d__89.MoveNext()
    at System.Threading.Tasks.ValueTask`1.get_Result()
    at System.Net.Http.RedirectHandler.d__4.MoveNext()
    at System.Net.Http.HttpClient.<g__Core|83_0>d.MoveNext()
    at ApiConsumerApp.Program.d__2.MoveNext() in C:\FKApps\BasicAuthenticationDemo\APIConsumerApp\Program.cs:line 64
    at ApiConsumerApp.Program.d__1.MoveNext() in C:\FKApps\BasicAuthenticationDemo\APIConsumerApp\Program.cs:line 31

    This exception was originally thrown at this call stack:
    [External Code]

    Inner Exception 1:
    AuthenticationException: Cannot determine the frame size or a corrupted frame was received.

Leave a Reply

Your email address will not be published. Required fields are marked *