'How to create a ApiKey authentication scheme for .NET Core
I trying to set ApiKey authentication scheme to my Api but can't find any post or documentation about it.
Closer thing i found is this Microsoft page, but nothing say on how to register a authentication handler.
I'm using .Net Core 6.
Solution 1:[1]
Found out a solution mostly based on this great Joonas Westlin guide to implement basic authentication scheme. Credits should go to him.
Steps:
1. Implement the options class inheriting from `AuthenticationSchemeOptions` and other boiler classes that will be need after.
2. Create the handler, inherit from `AuthenticationHandler<TOptions>`
3. Override handler methods `HandleAuthenticateAsync` to get the key and call your implementation of `IApiKeyAuthenticationService`
4. Register the scheme with `AddScheme<TOptions, THandler>(string, Action<TOptions>)` on the `AuthenticationBuilder`, which you get by calling `AddAuthentication` on the service collection
5. Implement the `IApiKeyAuthenticationService` and add it to Service Collection.
Here all the code.
The AuthenticationSchemeOptions and other boiler classes:
//the Service interface for the service that will get the key to validate against some store
public interface IApiKeyAuthenticationService
{
Task<bool> IsValidAsync(string apiKey);
}
//the class for defaults following the similar to .Net Core JwtBearerDefaults class
public static class ApiKeyAuthenticationDefaults
{
public const string AuthenticationScheme = "ApiKey";
}
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions{}; //Nothing to do
public class ApiKeyAuthenticationPostConfigureOptions : IPostConfigureOptions<ApiKeyAuthenticationOptions>
{
public void PostConfigure(string name, ApiKeyAuthenticationOptions options){} //Nothing to do
};
The handler:
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private const string AuthorizationHeaderName = "Authorization";
private const string ApiKeySchemeName = ApiKeyAuthenticationDefaults.AuthenticationScheme;
private readonly IApiKeyAuthenticationService _authenticationService;
public ApiKeyAuthenticationHandler(
IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IApiKeyAuthenticationService authenticationService)
: base(options, logger, encoder, clock)
{
_authenticationService = authenticationService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey(AuthorizationHeaderName))
{
//Authorization header not in request
return AuthenticateResult.NoResult();
}
if (!AuthenticationHeaderValue.TryParse(Request.Headers[AuthorizationHeaderName], out AuthenticationHeaderValue? headerValue))
{
//Invalid Authorization header
return AuthenticateResult.NoResult();
}
if (!ApiKeySchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
{
//Not ApiKey authentication header
return AuthenticateResult.NoResult();
}
if ( headerValue.Parameter is null)
{
//Missing key
return AuthenticateResult.Fail("Missing apiKey");
}
bool isValid = await _authenticationService.IsValidAsync(headerValue.Parameter);
if (!isValid)
{
return AuthenticateResult.Fail("Invalid apiKey");
}
var claims = new[] { new Claim(ClaimTypes.Name, "Service") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.Headers["WWW-Authenticate"] = $"ApiKey \", charset=\"UTF-8\"";
await base.HandleChallengeAsync(properties);
}
}
The extensions to make it easy to register the scheme:
public static class ApiKeyAuthenticationExtensions
{
public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder)
where TAuthService : class, IApiKeyAuthenticationService
{
return AddApiKey<TAuthService>(builder, ApiKeyAuthenticationDefaults.AuthenticationScheme, _ => { });
}
public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, string authenticationScheme)
where TAuthService : class, IApiKeyAuthenticationService
{
return AddApiKey<TAuthService>(builder, authenticationScheme, _ => { });
}
public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, Action<ApiKeyAuthenticationOptions> configureOptions)
where TAuthService : class, IApiKeyAuthenticationService
{
return AddApiKey<TAuthService>(builder, ApiKeyAuthenticationDefaults.AuthenticationScheme, configureOptions);
}
public static AuthenticationBuilder AddApiKey<TAuthService>(this AuthenticationBuilder builder, string authenticationScheme, Action<ApiKeyAuthenticationOptions> configureOptions)
where TAuthService : class, IApiKeyAuthenticationService
{
builder.Services.AddSingleton<IPostConfigureOptions<ApiKeyAuthenticationOptions>, ApiKeyAuthenticationPostConfigureOptions>();
builder.Services.AddTransient<IApiKeyAuthenticationService, TAuthService>();
return builder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
authenticationScheme, configureOptions);
}
}
Implement the Authentication service to validate the key against your config file or other store:
public class ApiKeyAuthenticationService : IApiKeyAuthenticationService
{
public Task<bool> IsValidAsync(string apiKey)
{
//Write your validation code here
return Task.FromResult(apiKey == "Test");
}
}
Now to use only is need to add this at start:
//register the schema
builder.Services.AddAuthentication(ApiKeyAuthenticationDefaults.AuthenticationScheme)
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationDefaults.AuthenticationScheme, null);
//Register the Authentication service Handler that will be consumed by the handler.
builder.Services.AddSingleton<IApiKeyAuthenticationService,ApiKeyAuthenticationService>();
Or in a more elegant way using the extensions:
builder.Services
.AddAuthentication(ApiKeyAuthenticationDefaults.AuthenticationScheme)
.AddApiKey<ApiKeyAuthenticationService>();
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
