API Key Authentication with ASP.NET Core

API key authentication uses an API key to authenticate the client of an HTTP-based API. An API key is a unique identifier or secret token that is provided to the API using many mechanisms, such as through the query string, request header, or as a cookie. ASP.NET Core does not have built-in support for API key authentication, so in this post, I will demonstrate how to secure an ASP.NET Core Web API using API key authentication.

Storing API keys

The API key needs to be protected. Tools such as the Secret Manager tool can be used to store sensitive data during development. Within your project directory, use the following command to enable secret storage:

dotnet user-secrets init

Next, add the API key to secret storage using the following command:

dotnet user-secrets set "ApiKey" "27d34740-c3d4-4938-9260-b5ba3a62922c"

Securing requests

With ASP.NET Core, filters can be used to run code before or after specific stages of the request processing pipeline. In this section, you will create a custom filter to handle API key authentication. Create a new file named ApiKeyAttribute.cs and add the following code:

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AwesomeApi.Filters;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ApiKeyAttribute : Attribute, IAuthorizationFilter
{
    private const string API_KEY_HEADER_NAME = "X-API-Key";

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var submittedApiKey = GetSubmittedApiKey(context.HttpContext);

        var apiKey = GetApiKey(context.HttpContext);

        if (!IsApiKeyValid(apiKey, submittedApiKey))
        {
            context.Result = new UnauthorizedResult();
        }
    }

    private static string GetSubmittedApiKey(HttpContext context)
    {
        return context.Request.Headers[API_KEY_HEADER_NAME];
    }

    private static string GetApiKey(HttpContext context)
    {
        var configuration = context.RequestServices.GetRequiredService<IConfiguration>();

        return configuration.GetValue<string>($"ApiKey");
    }

    private static bool IsApiKeyValid(string apiKey, string submittedApiKey)
    {
        if (string.IsNullOrEmpty(submittedApiKey)) return false;

        var apiKeySpan = MemoryMarshal.Cast<char, byte>(apiKey.AsSpan());

        var submittedApiKeySpan = MemoryMarshal.Cast<char, byte>(submittedApiKey.AsSpan());

        return CryptographicOperations.FixedTimeEquals(apiKeySpan, submittedApiKeySpan);
    }
}

The ApiKey attribute can be applied globally, to selected controllers, or individual actions. It is responsible for the following:

  • Retrieving the values of the submitted and the actual API keys
  • Ensuring that the submitted API key is valid (not blank) and that it matches the actual API key
  • Returning an UnauthorisedResult (401) if the submitted API key is invalid

Applying the ApiKey attribute is easy. In the example below, the attribute has been applied to the WeatherForecastController:

[ApiKey]
public class WeatherForecastController : ControllerBase
{
    ...
}

Applying the attribute at the controller level results in API key authentication being enforced for every action within the controller. So, access will be limited to requests that provide a valid API key. In the following example, the attribute has been applied to a specific action method:

[ApiKey]
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
    ...
}

So for this specific action, API key authentication will limit access to requests that include a valid API key. If you would like to apply API key authentication globally, update Program.cs with the following code:

builder.Services.AddControllers(options =>
    options.Filters.Add<ApiKeyAttribute>());

This results in the attribute being applied to every controller, automatically. API key authentication will therefore be enforced on all controllers.

Testing the API

There are many methods for testing your API including Postman, cURL, and PowerShell. The following example uses PowerShell and Invoke-WebRequest command:

Invoke-WebRequest -Method GET -Header @{"X-API-Key" = "27d34740-c3d4-4938-9260-b5ba3a62922c"} -Uri "https://localhost:5001/WeatherForecast"

The next example uses a curl command to make the same request:

curl -X 'GET' 'https://localhost:5001/WeatherForecast' -H 'accept: application/json' -H 'X-API-Key: 27d34740-c3d4-4938-9260-b5ba3a62922c'

Next steps

In this post, I demonstrated how to secure an ASP.NET Core API using API key authentication. If you would like to learn more, take a look at the following resources:

Thanks for reading, please feel free to post any questions or comments below.