ASP.NET Core: Using API Key Authentication by using Middlewares

In this article, we will implement the ASP.NET Core 8 Authentication using API Key. There are several developers widely use API key authentication to secure API endpoints. To access the API the client must send the valid API Key. This API key is validated on the server once the client sends it in the HTTP request. The client can send the API key in HTTP request in Request Headers, Request Body, and using the Query Parameter. The Figure 1 explains the API Key based authentication implementation



Figure 1: The API Key Authentication 

As shown in Figure 1, the execution takes place as stated in the following points, (the following numbering shows numbers displayed in Figure 1)

  1. The client makes an HTTP request by adding a custom header with an API key in it.
  2. The request is accepted and it passes through dependencies where the ASP.NET Core DI Registers services for Authentication, Authorization, etc. Make sure that the Authentication and Authorization services are registered in the container to make sure that the API is secured.
  3. The request is further passed to the HTTP Runtime where Middlewares is configured to process the request. 
  4. Here we can configure the Custom Middleware that will validate the request for Authentication by reading the Custom Header from the incoming HTTP request extracting the API Key from it and using it for the validation.
  5. If the key is valid the API Logic will be executed and if the Key validation is failed then the unauthorized response will be returned.
  6. The response is generated based on the Key validation.
 Technically, The API key authentication can be implemented using various ways as follows:

- Custom Attribute

- Custom Middleware

- Endpoint filters

- Policy-based Authorization

In this article, we will see the implementation using the Custom Middleware. The advantage of using the Middleware approach is that it allows us to implement and keep the authentication logic separate from the actual application code. This provides a better separation of concerns and reusability. API key authentication using the Middleware-based approach is suitable for larger projects those are having multiple endpoints and complex authentication requirements.

Step 1: Open Visual Studio 2022 and create a new ASP.NET Cor API Project targetted to .NET 7 or .NET 8. In this project, add a new folder and name it Services. In this folder, add a new Interface and name it as IApiAuthKeyValidatorService. This interface will have a method to validate the API Key. The Listing 1 shows the code for this interface.


namespace API_Authentication_ApiKey.Services
{
    public interface IApiAuthKeyValidatorService
    {
        void ValidateApiAuthKey(string apiAuthKey, out bool isValid);
    }
}

Listing 1: The IApiAuthKeyValidatorService code

Step 2: Let's modify the appsettings.json file by adding a key/Value pair for the API as shown in Listing 2. Please make sure that in real-world applications, this key must be stored in the environment variables, or for Azure cloud applications, this key must be stored in Azure Key Vault.


 "ApiAuthHeader": "X-API-AuthKey",
 "ApiAuthKey": "d3vnwDLqBCEcNkLY0uIwYg5dzyIAcMtSdwt7DuzdLc="

Listing 2: appsettings.json file that stores the API Key

Step 3: In the Services folder, add a new class file and name it as ApiAuthKeyValidatorService.cs. In this class file, we will write code for  ApiAuthKeyValidatorService class. This class will implement the IApiAuthKeyValidatorService interface. This class will be a constructor injected with the IConfguration interface. This will be used to read key/value pairs from the appsettings.json file. The ValidateApiAuthKey() method of this class contains logic to verify the received API key with the Key value read from the appsettings.json. The code for this class is shown in Listing 3.

namespace API_Authentication_ApiKey.Services
{
    public class ApiAuthKeyValidatorService : IApiAuthKeyValidatorService
    {
        IConfiguration _configuration;
        /// <summary>
        /// Inject the IConfiguration to read ApiAuthKey from appsettings.json
        /// </summary>
        /// <param name="configuration"></param>
        public ApiAuthKeyValidatorService(IConfiguration configuration)
        {
            _configuration = configuration;           
        }
        void IApiAuthKeyValidatorService.ValidateApiAuthKey(string apiAuthKey, out bool isValid)
        {
            isValid = false;
            // Make apiAuthKey is not Null or EMpty

            if (string.IsNullOrEmpty(apiAuthKey))
                isValid = false;

            // Read the Key from the appsettings.json
            string? key = _configuration.GetValue<string>("ApiAuthKey");
            // Make sure that 
            if (string.IsNullOrEmpty(key))
                isValid = false;
            if (key.Equals(apiAuthKey))
                isValid = true;
           
        }
    }
}


Listing 3: The Logic to Validate the API Key

Step 4: Modify the Program.cs to register the ApiAuthKeyValidatorService class in the DI Container so that we can use it in the application Middleware. The code is shown in Listing 4.

     .....
 builder.Services.AddTransient<IApiAuthKeyValidatorService, ApiAuthKeyValidatorService>();
     .....
  

Listing 4: The registration of ApiAuthKeyValidatorService class in DI Container

Step 5: As we discussed at the beginning of this article we will be using the Middleware to perform API Key authentication, to add the middleware to the project, add a new folder named 'Middlewares' in the project. In this folder, add a new class file and name it as ApiAuthKeyMiddleware.cs. In this class file, we will add code for the ApiAuthKeyMiddleware class. This class will be a constructor injected with the RequestDelegate delegate and the IConfiguration interface. The RequestDelegate will be used by the HTTP Pipeline to execute the Middleware logic. We are injecting the IConfiguration interface to read the value for the API Key header that is stored in the appsettings.json file. The InvokeAsync() method of the middleware class contains the logic for the API Key Authentication as shown in Listing 5


using API_Authentication_ApiKey.Services; using System.Net; namespace API_Authentication_ApiKey.Middlewares { public record ErrorMessage { public int StatusCode { get; set; } public string? Message { get; set; } } public class ApiAuthKeyMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; private readonly IApiAuthKeyValidatorService _service; /// <summary> /// Inject the Required Dependencies /// </summary> public ApiAuthKeyMiddleware(RequestDelegate next,
                IConfiguration configuration, IApiAuthKeyValidatorService service) { _next = next; _configuration = configuration; _service = service; } /// <summary> /// Method to validate the ApiKey /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task InvokeAsync(HttpContext context) { try { // Read the Header NAme from the appsettings.json string? headerName = _configuration.GetValue<string>("ApiAuthHeader"); if (string.IsNullOrEmpty(context.Request.Headers[headerName])) throw new Exception("Missing Header Values on the server"); // Make sure that the ApiAuthHeader is present in the HTTP Request Header if (string.IsNullOrEmpty(context.Request.Headers[headerName])) throw new Exception("Missing Header Values in the request"); // Read the Key string? apiAuthKey = context.Request.Headers[headerName]; // Validate the Key _service.ValidateApiAuthKey(apiAuthKey, out bool isValid); // Return the UnAuthorized Response if (!isValid) { context.Response.StatusCode = (int) HttpStatusCode.Unauthorized; ErrorMessage errorMessage = new ErrorMessage() { StatusCode = context.Response.StatusCode, Message = "The supplied Authorization Information is invalid", }; await context.Response.WriteAsJsonAsync(errorMessage); return; } await _next(context); } catch (Exception ex) { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; ErrorMessage errorMessage = new ErrorMessage() { StatusCode = context.Response.StatusCode, Message = ex.Message, }; await context.Response.WriteAsJsonAsync(errorMessage); } } } // The Middleware class public static class ApiAuthKeyMiddlewareExtension { public static void UseApiKeyAuthorization(this IApplicationBuilder builder) { builder.UseMiddleware<ApiAuthKeyMiddleware>(); } } } 
Listing 5: The ApiAuthKeyMiddleware Logic code to perform API Key Authentication
As shown in Listing 5, the InvokeAsync() method reads the 'ApiAuthHeader' key from the appsettings.json to verify if the HTTP Request header contains the custom header key as 'X-API-AuthKey'. If this key is missing then an exception is thrown. If the key is present the value of the key is retrieved from the headers and its is passed to the ValidateApiAuthKey() method of the 'IApiAuthKeyValidatorService' interface. If the API Key is not matched then the ErrorMessage will be written to the HTTP response using the 'ErrorMessage' class by adding a response code as 'Unauthorized'. If the API Key matches then the execution will be continued. The Listing 5 also contains code for 'ApiAuthKeyMiddlewareExtension'. This class is used to regisetr the 'ApiAuthKeyMiddleware' class as Custom Middleware in the ASP.NET Core middleware pipeline.  This class has the 'UseApiKeyAuthorization()' method to register the 'ApiAuthKeyMiddleware' class as Custom Middleware.

Step 6: Modify the Program.cs by adding the code for Swagger API Definition, Authentication, and Authorization Services, and also make sure that the 'UseApiKeyAuthorization()' method is called before the HttpsRedirection middleware. Also, use the UseAuthrization() middleware. The code of Program. cs is shown in Listing 6:

using API_Authentication_ApiKey.Middlewares;
using API_Authentication_ApiKey.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// The API Key Authentication Definition for Swagger 
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Implementing Api Key Authentication", Version = "v1" });
    c.AddSecurityDefinition("APIAuthKey", new OpenApiSecurityScheme
    {
        Description = "The Auth ApiKey must appear in HTTP Request header",
        Type = SecuritySchemeType.ApiKey,
        Name = "X-API-AuthKey",
        In = ParameterLocation.Header,
        Scheme = "ApiKeyScheme"
    });
    var key = new OpenApiSecurityScheme()
    {
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "APIAuthKey"
        },
        In = ParameterLocation.Header
    };
    var requirement = new OpenApiSecurityRequirement
                    {
                             { key, new List<string>() }
                    };
    c.AddSecurityRequirement(requirement);
});
// Authentication and Authorization Services
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
builder.Services.AddTransient<IApiAuthKeyValidatorService, ApiAuthKeyValidatorService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
// Register the Middleware
app.UseApiKeyAuthorization();
app.UseHttpsRedirection();

app.UseAuthorization();



app.MapControllers();
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/wf", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();


app.Run();

internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Listing 6: The Program.cs class complete code

Run the application. The Swagger page will show the 'Authorize' button. When this button is clicked, it will show the dialog box where the API Key can be entered for Authentication as shown in Figure 2





Figure 2: The Authorize dialog box

Once the authentication is completed, click on the Try it out button for 'wf' API, if the authentication is successful, then the response will be shown else the UnAuthorized error will be responded to as shown in Figure 3



Figure 3: The Response

If the API Key is invalid or not passed then the middleware will send the response as UnA-Authorized as shown in Figure 4



Figure 4: Un-Authorized Response

The code for the article is available on 'this link'


Conclusion: API Key authentication is one of the best mechanisms to protect APIs. The middleware approach for implementing the API Key authentication makes it suitable for bigger applications.

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach