API Key Authentication Middleware In ASP NET Core – A How To Guide

While putting some special content together, I found myself needing to integrate API key authentication middleware into my ASP.NET Core application. I’ve written before about using custom middleware in ASP.NET, but I’ve also been trying to do a better job of documenting smaller code changes like this so the next time I’m looking I have an easy reference.

And that means YOU get some free code and a reference guide to go along with it!


What’s In This Article: API Key Authentication Middleware

Remember to check out these platforms:


What is Middleware in ASP.NET Core?

Middleware in ASP.NET Core is code that you can add into an application’s pipeline to handle requests and responses. Each of these components can:

  • Choose whether to pass the request to the next component in the pipeline
  • Can perform work before and after the next component in the pipeline.

When building an ASP.NET Core application, middleware can be used to implement functionality such as authentication, error handling, logging, and serving static files. The list goes on, and you can even implement your own custom middleware. If you’d like more information on middleware to get started, you can check out this article that I’ve written:

For our use case here, we’ll be looking at adding our own custom middleware to an ASP.NET Core application. Given that we want to do some type of authentication with an API key, we should be able to create middleware that STOPS processing if the key is invalid or CONTINUE processing if the key is valid. Should be simple enough, right? Let’s continue on and see!


API Key Authentication Middleware Example Code

Now that you understand what middleware in ASP.NET core is, let’s look at an example. For my application, I wanted to handle the API key from the request header. Of course, this could be done in different ways, like having a query parameter for example, but the header was what I decided to go with.

Here’s the example API key authentication middleware in this code snippet:

public async Task MyMiddleware(
    HttpContext context,
    RequestDelegate next)
{
    // if you wanted it as a query parameter, you'd
    // want to consider changing this part of the code!
    const string ApiKeyHeader = "<YOUR_HEADER_KEY_HERE>";
    if (!context.Request.Headers.TryGetValue(ApiKeyHeader, out var apiKeyVal))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Api Key not found.");
        return;
    }

    const TheRealApiKey = "<YOUR_SECRET_API_KEY_HERE>";
    if (!string.Equals(TheRealApiKey, apiKeyVal, StringComparison.Ordinal))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Invalid API Key.");
        return;
    }

    await next(context);
}

The code above is very much representative of the chain of responsibility design pattern! Our middleware handler can take action, and then is able to call into the next delegate using the context that was passed in — or it can terminate the sequence by writing a response back and leaving the method without calling the next handler. But if you’re wondering where we hook this up, then check this code snippet:

// "app" here is the WebApplication instance after
// being built by the web application builder
app.Use(MyMiddleware);

Odds are you do NOT want to have your API key right in your code — in fact, I would say “PLEASE DO NOT DO THIS!” so that you don’t accidentally commit a secret API key into your source control. A way that we can work around this is by using environment variables and the app configuration! So in the following code example, you can see a string value being looked up in the application’s configuration:

// "app" here is the WebApplication instance after
// being built by the web application builder
var config = app.Configuration
var secretApiKey = config.GetValue<string>("<CONFIG_KEY_NAME>");

Now instead of comparing against a hard-coded API key directly in your code, you can read it from a configuration instance, which can be populated by environment variables!


Using This With Plugin Architecture in C#

It’s no secret that I like using plugin architectures when I’m developing software — but this might be your first time reading some of my content. So spoiler alert: I love using plugins when designing software systems.

In the ASP.NET Core application that I wanted to add my API key authentication middleware to, I have a very simple loading scheme that allows dependencies to “hook” into the web application building process. This is accomplished by a couple of basic steps:

  • I use Autofac and assembly scanning to look for DLLs with Autofac modules to load in at startup time
  • These DLLs become plugins, so by definition any plugin can then use the dependency container builder to register their own dependencies
  • Part of that registration process means they can ask the container for the web application instance that was built
  • Once they have that web application instance, they are able to hook up their middleware to it.

This is a very simple/naive way to do this because there’s no ordering or control on the middleware. If certain middleware MUST be registered before/after other middleware, there’s no opportunity to say so — You just buckle up and hope for the best. However, that’s okay for now because my middleware needs are EXTREMELY limited currently. I will create an ordering scheme for this if/when necessary.

So what does my plugin code look like? Pretty much what we saw earlier, except inside of an Autofac module:

internal sealed class ApiKeyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder
            .Register(ctx =>
            {
                var app = ctx.Resolve<WebApplication>();
                var config = ctx.Resolve<IConfiguration>();

                const string ApiKeyConfigKey = "API_CONFIG_KEY_NAME";
                Lazy<string?> lazyApiKey = new(() => config.GetValue<string>(ApiKeyConfigKey));

                app.Use(async (context, next) =>
                {
                    const string ApiKeyHeader = "HEADER_KEY_NAME";
                    if (!context.Request.Headers.TryGetValue(
                        ApiKeyHeader,
                        out var apiKeyVal))
                    {
                        context.Response.StatusCode = 401;
                        await context.Response.WriteAsync("Api Key not found.");
                        return;
                    }

                    var apiKey = lazyApiKey.Value;
                    if (!string.Equals(apiKey, apiKeyVal, StringComparison.Ordinal))
                    {
                        context.Response.StatusCode = 401;
                        await context.Response.WriteAsync("Invalid API Key.");
                        return;
                    }

                    await next(context);
                });

                // this is used to help control the ordering of dependencies
                // so the application won't start until all of these
                // have been resolved!
                return new PostAppRegistrationDependencyMarker();
            })
            .SingleInstance();
    }
}

If you’re wondering what the benefit is: I never had to touch any other code in my project and I instantly added API authentication to every single route. This code *is* currently in the main assembly, but I could have added it to a new project, built the DLL, and dropped it into the running directory to get this behavior activated.

That’s the power of building with plugins!


Wrapping Up API Key Authentication Middleware

Overall, adding API key authentication middleware in ASP.NET Core applications is quite simple! The middleware can be added directly onto the application instance and you can decide how you’d like to load your secret API key to compare against. By sprinkling in a little bit of C# plugin architecture like I prefer to use in many of my projects, it’s a breeze to extend the functionality of your ASP.NET Core app!

If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Meet other like-minded software engineers and join my Discord community!

Affiliations:

These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!

      • RackNerd: Cheap VPS hosting options that I love for low-resource usage!
      • Contabo: Alternative VPS hosting options with very affordable prices!
      • ConvertKit: This is the platform that I use for my newsletter!
      • SparkLoop: This service helps me add different value to my newsletter!
      • Opus Clip: This is what I use for help creating my short-form videos!
      • Newegg: For all sorts of computer components!
      • Bulk Supplements: For an enormous selection of health supplements!
      • Quora: I try to answer questions on Quora when folks request them of me!


    Frequently Asked Questions: API Key Authentication Middleware

    What is middleware in ASP.NET Core?

    Middleware in ASP.NET Core is software that is assembled into an application pipeline to handle requests and responses. Middleware components can choose to pass the request to the next component, perform actions before and after it, and are essential for implementing features like authentication, logging, and static file serving.

    How do you implement API key authentication using middleware?

    To implement API key authentication using middleware, create a middleware component that checks for the API key in the request headers. If the key is absent or invalid, the middleware stops further processing and returns a 401 Unauthorized response. Otherwise, it passes the request to the next component in the pipeline.

    What is the best practice for handling API keys in production environments?

    In production environments, it is best practice to avoid hardcoding API keys directly in the code. Instead, use environment variables or secure app configurations to store them. This method enhances security by keeping sensitive information out of the source code and version control systems.

    author avatar
    Nick Cosentino Principal Software Engineering Manager
    Principal Software Engineering Manager at Microsoft. Views are my own.

    Leave a Reply