Authentication for ASP.NET Core Security using HTTP header like X-APIKEY
and
simple single provided key.
This authentication method, while very simple, is of course not suitable for production systems due to potential insecurities. However, as fast and simple method, still could be useful in situations you don't wan't to use token-based security.
Configure your authentication method in Program.cs
:
// ...
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
})
.AddApiKeyHeaderAuthentication(options => options.ApiKey = "my-secret-api-key");
Remember about activating it later:
app.UseAuthentication();
app.UseAuthorization();
It requires you to authenticate to sent X-APIKEY
header along with your
request, with a value equal to the secret key you set in options, or the request
will fail with 401 Unauthorized
HTTP error.
Of course, you have to ensure your controller or actions are expecting user to
be authenticated, for example you can use [Authorize]
.
See Microsoft docs for more.
In ASP.NET Core 7.0, when using only one AuthenticationScheme
, you can use
simpler version:
builder.Services.AddAuthentication()
.AddApiKeyHeaderAuthentication(options => options.ApiKey = "my-secret-api-key");
You can use any header, by configuring options, e.g. you can set you want
x-api-key
, not X-APIKEY
:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
})
.AddApiKeyHeaderAuthentication(options => { options.ApiKey = "my-secret-api-key"; options.Header = "x-api-key"; });
// ...
You may use option CustomAuthenticationHandler to provide a function which will be used to perform a validation of your key. For example let's use a simple method:
private (bool, string) SimpleCustomAuthenticationLogic(string apiKey)
{
if (apiKey == "abc123") return (true, "Jon");
if (apiKey == "def234") return (true, "Snow");
return (false, string.Empty);
}
Your function must be matching CustomApiKeyHandlerDelegate
delegate -- input
parameters is a string, the user-provided API key, the result should be a tuple
of bool and string -- where the bool value is telling if the authentication is
successful, and the string value is the user name to be used in
AuthenticationTicket.Principal.Identity.Name
, so you may differentiate about
it later.
In Startup.cs you may provide a type as a singleton, implementing an
IApiKeyCustomAuthenticator
interface, which will be acquired from current
request services to be used for authentication. You have to set option for
UseRegisteredAuthenticationHandler
to true.
In that case, you may use it for database connection, checking users API key usage limits or similar things before authentication.
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
})
.AddApiKeyHeaderAuthentication(options => options.UseRegisteredAuthenticationHandler = true);
builder.Services.AddSingleton<TestApiKeyService>();
The interface requires for you implementation of a method returning a pair of
bool and string, where bool indicates if the authentication was successful, and
in the string you may provide a name, which will be used in created
AuthenticationTicket.Claims
as Principal.Identity.Name
so you may
differentiate between different users.
In this example, a simple ILogger<T>
is used:
internal class TestApiKeyService : IApiKeyCustomAuthenticator
{
private readonly ILogger logger;
public TestApiKeyService(ILogger<TestApiKeyService> logger)
{
this.logger = logger;
this.logger.LogInformation("Created a test authenticator");
}
// returns true on "testapi", returns uppercase key as name, false in any other case
public CustomApiKeyHandlerDelegate CustomAuthenticationHandler => (key) =>
{
logger.LogDebug($"Someone tried to authenticate with API key: {key}");
return key == "testapi" ? (true, key.ToUpper()) : (false, null);
};
}
If UseRegisteredAuthenticationHandler
is set to true and there is a registered
singleton implementing an IApiKeyCustomAuthenticatorFullTicket
interface, it
will be used automatically.
This interface allows to create a custom method returning full
AuthorizationTicket
as you need - e.g. with proper claims or roles you are
using later.
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme;
})
.AddApiKeyHeaderAuthentication(options => options.UseRegisteredAuthenticationHandler = true);
builder.Services.AddSingleton<CustomFullTicketHandler>();
The class implementing IApiKeyCustomAuthenticatorFullTicket
is being created
with dependency injection, so it may access database or other services useful
for your own authentication logic.
The interface requires for you implementation of a method returning an
AuthorizationTicket
, which you can customize, for example:
internal class CustomFullTicketHandler : IApiKeyCustomAuthenticatorFullTicket
{
public AuthenticateResult CustomAuthenticationHandler(string key)
{
if (key == "goodkey")
return AuthenticateResult.Success(CreateAuthenticationTicket());
return AuthenticateResult.NoResult();
}
private AuthenticationTicket CreateAuthenticationTicket()
{
var claims = new[] {
new Claim(ClaimTypes.Name, "Jon Snow"),
new Claim(ClaimTypes.Role, "testrole")
};
var identity = new ClaimsIdentity(claims, ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, ApiKeyHeaderAuthenticationDefaults.AuthenticationScheme);
ticket.Properties.RedirectUri = "http://localhost";
return ticket;
}
}
Mixing options of Options.ApiKey
, Options.CustomAuthenticationHandler
and
Options.UseRegisteredAuthenticationHandler
may result in unexpected results,
please treat them as mutually exclusive.
- If the header is empty, no logic is being applied;
- If not and
CustomAuthenticationHandler
is not null andUseRegisteredAuthenticationHandler
is false,CustomAuthenticationHandler
(method) is used; - If not and
CustomAuthenticationHandler
is true and handler of typeIApiKeyCustomAuthenticator
is registered, it will be used; - If not and
CustomAuthenticationHandler
is true and handler returning tickets (IApiKeyCustomAuthenticationTicketHandler
) is registered, it will be used; - If not, but the header field value is equal to
Options.ApiKey
andUseRegisteredAuthenticationHandler
is false, Options.ApiKey will be used.
In any other case, NoResult()
is returned.
Licensed under MIT
Have a lot of fun. --ktos