In this post, we are going to see how we can add security to our services that communicate machine to machine, as when we have this scenario we don't use JWT or other authentication methods.
Index
1 - What is an API Key?
An API Key is a unique identifier used to identify and authenticate an application or user.
As its name suggests, this key is used to allow access to an API, and the API Key is limited to giving access to the application or user. Unlike JWT, the API key does not expire and we always use the same one. For this reason, both the caller and the receiver of the call know the key in question.
Although I mentioned that the API key can identify who makes the call, the reality is that this “who” is actually “which system”, since the API key is used in 99.9% of cases for system-to-system calls, whether it's an external or internal API. They are usually used when actions happen automatically.
Also, the keys are usually 1 to 1, meaning you have one app, you have one key, and that key allows you to identify yourself to the application you are querying.
Personally, I don't think the API key is enough for identification and permissions. For me, an API key should give you access to everything (to identify), and then limitations should be applied. However, an API key does not give you the same control as a token when, for example, allowing access to X or Y. Obviously, the application owner of the API can store that information in a database, but for that, you would use JWT or an API Token.
For example, the YouTube API follows this mechanism: it gives you a key for your specific application; you do not have a username and password as such.
1.1 - What happens with a compromised API key?
You need to change it. That API identifies your machine, so the machine you're calling will have no idea if it really is your machine or if it's a third party trying to gather information.
This especially happens in public APIs, which you can call from anywhere. In a private company, it is most common to have access limited from certain IPs. For example, company A is located at ip 25
and company B, the creator of the API, has configured it so that only calls from ip25
are accepted.
Usually, companies do it this way, obviously for security. In any case, if the key has been compromised, you should generate a new one.
2 - When to use an API key
As I explained earlier, it is used to control who and how much uses the API, to ensure that no one is abusing our API or trying to do anything malicious.
It also allows us to block anonymous traffic.
3 - When NOT to use an API key
We should avoid using it for identifying the specific user making the call; for that, we use the JWTs mentioned earlier.
Also, remember that the API key is per application, not per user.
4 - Implementation of an API key in .NET
Now we are going to look at a simple example of how to implement an API Key within a microservices environment using .NET
For this example, I’m going to keep it as simple as possible, a single client with a single token, so we are not even storing it in memory or anything like that, we simply have it configured in our appsettings.json
. This way we simplify things.
This code is within the Distribt project, which you can find on GitHub, and we're going to add the use of API Keys to the architecture, within our API to which the end client has access.
Here you can see an image of the full system architecture, but for this post we will only focus on the green box.
If you are not familiar with distributed systems, I recommend following this course:
What the API does is, using YARP (reverse proxy), route the call from that microservice to the internal one, so the client only has access to that microservice.
In our case, we'll test with the health checks endpoint https://localhost:7022/reports/health
So, let's get to it. What we need to do is create the API Key value in our appsettings
. In a production environment, you'd generate this API Key semi-automatically for each client, expiring old ones, etc.
{
...
"ApiKey": {
"clientId": "1",
"value": "b92b0bdf-da95-42a8-a2b1-780ca461aaf3"
}
}
With this, all you have to do is read the value, either by using the options pattern or directly from IConfiguration, and compare it to what arrives from the user.
To do this, retrieve the information:
public class ApiKeyConfiguration
{
public string? ClientId { get; init; }
public string? Value { get; init; }
}
// in the startup.cs class
services.Configure<ApiTokenConfiguration>(configuration.GetSection("ApiKey"));
And the next step is to create a middleware
or a FilterAttribute
that checks if that token comes in the request.
In our case, we're going to create a middleware
. But the logic for a FilterAttribute
is the same, you just have to enable it where you want to use it, while a middleware applies it to all endpoints by default.
As a note, the API key will come as part of the headers, specifically as part of the HTTP Authorization header (spec), but optionally you can include it as part of the query string, since many services do it that way.
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IOptions<ApiKeyConfiguration> apiToken)
{
if (context.Request.Headers.TryGetValue("apiKey", out StringValues apiKey))
{
if (apiKey == apiToken.Value.Value)
await _next(context);
else
ReturnApKeyNotfound();
}
else
{
ReturnApKeyNotfound();
}
void ReturnApKeyNotfound()
{
throw new UnauthorizedAccessException("The API Key is missing");
}
}
}
Now we just have to add the middleware; one thing to keep in mind is that the middleware will not be executed when calling the health
or health-ui
endpoint of the microservice, but it will run when calling the endpoint referenced by the microservice:
//Do not act on /health or /health-ui
webApp.UseWhen(context => !context.Request.Path.StartsWithSegments("/health"),
appBuilder => appBuilder.UseMiddleware<ApiKeyMiddleware>()
);
Now we just need to test it.
If we make a request from Postman with the wrong API key, or without an API key, we see it returns an error:
But when we send the correct value, it shows the expected result:
Conclusion
In this post we've seen what an API key is
When to implement an API Key
and how to implement an API Key in .net
If there is any problem you can add a comment bellow or contact me in the website's contact form