Secure Web API by Key Authentication in ASP.NET Core
Praful Chauhan
7+ Yrs | Full Stack Software Engineer | ReactJS | Redux | ASP.NET Core | Clean Architecture | Microservice | SQL | MongoDB | Postgres | Payment gateway integration | 3rd Party Supplier Integration
When you’re dealing with Web APIs or REST APIs, it’s important to keep the endpoints secure. One common way to do this is by using something called API Keys. API Keys are special codes that serve as a kind of password for accessing these endpoints. They’re usually stored in files like appsettings.json or other storage places. In this article, we’ll dive into how API Key Authentication works and learn about other related concepts in .NET Core.
Different Ways to Secure APIs with Keys in ASP.NET Core, We are passing the API key in the header with the name X-API-Key.
Let’s explore each method to see how they work.
First, let’s add the API key in the appsetting.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ApiKey": "08BxzdYdEgNCrRhMbCpkBF7e4d4Kib46dwL9ZE5egiL0iL5Y3dzREUBSUYVUwEcX"
}
We add the ApiKey configuration property with the random 64-bit string value.
For this we passing the API key as a custom header in the request. We include the API key in the header, such as X-API-Key.
Implementing API Key Authentication with Attribute:
The custom attribute helps us check API keys easily. We can put this attribute on specific parts of our code, like controllers or action methods.
Think of it as a security stamp. When a request comes in, this stamp checks if the API key is valid. If it is, the request goes through; if not, it gets blocked.
Let’s create a custom attribute called ApiKeyAttribute:
public class ApiKeyAttribute : ServiceFilterAttribute
{
public ApiKeyAttribute()
: base(typeof(ApiKeyAuthFilter))
{
}
}
This ApiKeyAttribute inherits some special powers from ServiceFilterAttribute. It’s like giving a job to someone (in this case, ApiKeyAuthFilter). This filter will do the actual checking.
In ApiKeyAuthFilter, we do the actual key check:
public class ApiKeyAuthFilter : IAuthorizationFilter
{
private readonly IApiKeyValidation _apiKeyValidation;
public ApiKeyAuthFilter(IApiKeyValidation apiKeyValidation)
{
_apiKeyValidation = apiKeyValidation;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
string userApiKey = context.HttpContext.Request.Headers[Constants.ApiKeyHeaderName].ToString();
if (string.IsNullOrWhiteSpace(userApiKey))
{
context.Result = new BadRequestResult();
return;
}
if(!_apiKeyValidation.IsValidApiKey(userApiKey))
context.Result = new UnauthorizedResult();
}
}
we register the ApiKeyAuthFilter as a scoped service in the DI container:
builder.Services.AddScoped<ApiKeyAuthFilter>();
This filter is like a gatekeeper. It checks if there’s a key in the request. If not, it sends back a “bad request” response. If the key is there but invalid, it sends back an “unauthorized” response.
After setting up, we attach the attribute to our action method:
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
[HttpGet]
[ApiKey]
public IActionResult Get()
{
return Ok();
}
}
Now, when we send a request to this endpoint with a valid key, we get a “200 OK” response. Easy, right?
Implementing API Key Authentication with Middleware:
In ASP.NET Core, middleware is like a gatekeeper for handling requests and responses. It creates a path for requests to travel through, letting us check, handle, and even change them along the way. By making our own middleware, we can add API key checks to our ASP.NET Core apps with flexibility and control.
Custom middleware acts like a security guard at the entrance of our app. It checks incoming requests for API keys before they reach the endpoint.
Let’s create our own security guard, called ApiKeyMiddleware:
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly IApiKeyValidation _apiKeyValidation;
public ApiKeyMiddleware(RequestDelegate next, IApiKeyValidation apiKeyValidation)
{
_next = next;
_apiKeyValidation = apiKeyValidation;
}
public async Task InvokeAsync(HttpContext context)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers[Constants.ApiKeyHeaderName]))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return;
}
string? userApiKey = context.Request.Headers[Constants.ApiKeyHeaderName];
if (!_apiKeyValidation.IsValidApiKey(userApiKey!))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return;
}
await _next(context);
}
}
In this ApiKeyMiddleware class, we set up a guard post. It takes in the next middleware component in the pipeline and an API key validator. Then, it has a method called InvokeAsync, which gets called for every request.
Inside InvokeAsync, we check if there’s an API key in the request headers. If not, we send back a “bad request” response. If the key is there but invalid, we send back an “unauthorized” response. Otherwise, we let the request continue to the next middleware component.
领英推荐
To use this custom middleware, we add it just before the app.UseHttpsRedirection()middleware in the Program class:
app.UseMiddleware<ApiKeyMiddleware>();
API Key Authentication Using Endpoint Filters:
Endpoint filters are like security guards stationed at specific entry points in our ASP.NET Core application. They allow us to check requests right before they enter the action methods.
Let’s create a guard called ApiKeyEndpointFilter:
public class ApiKeyEndpointFilter : IEndpointFilter
{
private readonly IApiKeyValidation _apiKeyValidation;
public ApiKeyEndpointFilter(IApiKeyValidation apiKeyValidation)
{
_apiKeyValidation = apiKeyValidation;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
if (string.IsNullOrWhiteSpace(context.HttpContext.Request.Headers[Constants.ApiKeyHeaderName].ToString()))
return Results.BadRequest();
string? apiKey = context.HttpContext.Request.Headers[Constants.ApiKeyHeaderName];
if (!_apiKeyValidation.IsValidApiKey(apiKey!))
{
return Results.Unauthorized();
}
return await next(context);
}
}
This ApiKeyEndpointFilter class is our guard. It’s set up to check requests at specific entry points. Inside, we have a method called InvokeAsync, which gets called every time an associated endpoint is accessed.
In InvokeAsync, we first check if there’s an API key in the request headers. If not, we send back a “bad request” response. If there is a key but it’s invalid, we send back an “unauthorized” response. Otherwise, we let the request continue to the next step in the pipeline.
To use this guard, we add it to our API endpoint in the Program.cs class:
app.MapGet("api/product", () =>
{
return Results.Ok();
}).AddEndpointFilter<ApiKeyEndpointFilter>();
With this setup, our guard checks every request to this specific endpoint for a valid API key. If the key is good, the request goes through and we get a successful response.
Policy-based Authorization With API Key Authentication:
Policy-based authorization is like setting up rules for who can enter a VIP area. We create specific rules, or policies, that determine who gets in based on certain conditions. In our case, we want to make sure that only requests with valid API keys can get through.
First, we create a special requirement class, ApiKeyRequirement, which serves as a marker indicating that API key authentication is needed. It’s like saying, “Hey, this is what we need for access.”
public class ApiKeyRequirement : IAuthorizationRequirement
{
}
Now, we need someone to check if the request meets this requirement. That’s where ApiKeyHandler comes in. It checks the request for a valid API key.
public class ApiKeyHandler : AuthorizationHandler<ApiKeyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IApiKeyValidation _apiKeyValidation;
public ApiKeyHandler(IHttpContextAccessor httpContextAccessor, IApiKeyValidation apiKeyValidation)
{
_httpContextAccessor = httpContextAccessor;
_apiKeyValidation = apiKeyValidation;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ApiKeyRequirement requirement)
{
string apiKey = _httpContextAccessor?.HttpContext?.Request.Headers[Constants.ApiKeyHeaderName].ToString();
if (string.IsNullOrWhiteSpace(apiKey))
{
context.Fail();
return Task.CompletedTask;
}
if (!_apiKeyValidation.IsValidApiKey(apiKey))
{
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
Here, ApiKeyHandler checks if there’s an API key in the request. If there isn’t, or if the key is invalid, it rejects the request. If the key is valid, it allows the request to go through.
To make all this work, we need to set up our application to understand these rules. We do this by registering ApiKeyRequirement and ApiKeyHandler in the Program.cs class.
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiKeyPolicy", policy =>
{
policy.AddAuthenticationSchemes(new[] { JwtBearerDefaults.AuthenticationScheme });
policy.Requirements.Add(new ApiKeyRequirement());
});
});
builder.Services.AddScoped<IAuthorizationHandler, ApiKeyHandler>();
Here, we’re telling our application about the rules we’ve set up. We say that there’s a policy called “ApiKeyPolicy” which requires API key authentication. We also register ApiKeyHandler to handle this policy.
Finally, we apply this policy to our endpoints. For example:
[HttpGet("all")]
[Authorize(Policy = "ApiKeyPolicy")]
public IActionResult GetEmployees()
{
return Ok();
}
With this setup, only requests with valid API keys can access the GetEmployees endpoint. If the API key is missing or invalid, the request gets rejected.
This approach helps us ensure that only authorized requests get through, based on our specific rules.
Conclusion:
API key authentication provides a straightforward and reliable way to secure API endpoints, especially for applications requiring basic security without added complexity. Its simplicity in implementation and effectiveness in managing access make it a preferred choice for many scenarios.
In ASP.NET Core, we’ve explored different methods of implementing API key authentication, each with its own benefits. By enforcing API key validation, we add an extra layer of security to our APIs, ensuring that only authorized users can access them.