I am attempting to build a super simple API-key authentication for certain APIs in a controller. For this I have this in ConfigureServices():
services.AddAuthorization(options =>
{
options.AddPolicy(
Auth.Constants.WebmasterPolicyName,
policy =>
policy.RequireAssertion(context =>
{
if (context.Resource is HttpContext httpContext)
{
if (httpContext.Request.Headers.TryGetValue("X-API-KEY", out var header))
{
var val = header.FirstOrDefault()?.ToLower();
if (val == "my-super-secret-key")
{
return Task.FromResult(true);
}
}
}
return Task.FromResult(false);
})
);
});
I have decorated an API with this:
[HttpDelete("{itemId:guid}")]
[Authorize(Policy = Auth.Constants.WebmasterPolicyName)]
public async Task<ActionResult> DeleteCatalogItemAsync(Guid itemId)
This works perfectly, when I set the correct API key in the request.
The problem is the negative case: When the key is missing or wrong, I will get a 500 error:
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at AlwaysOn.CatalogService.Startup.<>c__DisplayClass5_0.<<Configure>b__3>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
But I'm not sure what to do with that message. I would just like it to return a 401 response to the client.
You need to implement a custom authentication handler in order to properly handle this. Here is a good example on how to do it.
To summarize the idea, you need to register a custom authentication scheme:
builder.Services.AddAuthentication("ApiKey")
.AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationSchemeHandler>(
"ApiKey",
opts => opts.ApiKey = configuration.GetValue<string>("api-key")
);
And define a class for options:
public class ApiKeyAuthenticationSchemeOptions: AuthenticationSchemeOptions {
public string ApiKey {get; set;}
}
And implement a handler class ApiKeyAuthenticationSchemeHandler:AuthenticationHandler<ApiKeyAuthenticationSchemeOptions>.
With a method
protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
var apiKey = Context.Request.Headers["X-API-KEY"];
if (apiKey != Options.ApiKey) {
return Task.FromResult(AuthenticateResult.Fail("Invalid X-API-KEY"));
}
var claims = new[] { new Claim(ClaimTypes.Name, "VALID USER") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
And last, in your controller you can add an Authorize attibute, like this:
[Authorize(AuthenticationSchemes = "ApiKey")]
We can create a custom ApiKeyMiddleware to implemente simple API key authentication.
It is somehow similar to what we have done in the custom attribute, but the main difference that you will notice here is that we cannot directly set the Response object of the context but we have to assign the statuscode and message separately.
Sample Code:
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace SecuringWebApiUsingApiKey.Middleware
{
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string APIKEYNAME = "ApiKey";
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Api Key was not provided. (Using ApiKeyMiddleware) ");
return;
}
var appSettings = context.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!apiKey.Equals(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized client. (Using ApiKeyMiddleware)");
return;
}
await _next(context);
}
}
}
For more details, we can refer this blog.
Secure ASP.NET Core Web API using API Key Authentication
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With