Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET core - simple API key authentication

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.

like image 666
silent Avatar asked Dec 06 '25 06:12

silent


2 Answers

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")]

like image 187
SergVro Avatar answered Dec 07 '25 23:12

SergVro


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

like image 23
Jason Pan Avatar answered Dec 08 '25 00:12

Jason Pan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!