I have an ASP.NET Core 8 Web API project. I have it set up to allow access via user logins (using bearer tokens). This is set up using AuthorizationHandler
classes. I also have it set up to use API keys for access using AuthenticationHandler
classes. These are both working fine as of right now.
What I am trying to do is allow either method on a single endpoint. The idea is that if the endpoint is called with a valid API key then they would be let through, or if they called with a valid bearer token they would also be let through. If neither then return 403. I'm definitely not a pro at this stuff so sorry if I'm missing something obvious.
I found this and tried it. It seemed to almost work, but when both attributes were present, the bearer token one ended up with a user that had no claims so I couldn't check their permission level. As soon as I removed one attribute so that ONLY the bearer token attribute was present it worked normally and they had all their claims. I'm not sure I understand that but either way it didn't seem to work.
I also found this tutorial that looked promising. However, my bearer token is AuthorizationHandler
and API key is AuthenticationHander
so they can't inherit from a common class so I don't know that it will work that way either.
I also found other tutorial about using two different possible bearer token claims but again that's a bit different than what I'm looking for. It's really two different schemes for authenticating.
I could just define duplicate endpoints and have one be token and one be key but that seems like unnecessary bloat, plus the paths would have to be different so I'd have something like:
api/customer/{id}
api/customer/keyAuthentication/{id}
which just seems clunky and hard to manage. I want the same endpoint, I just want the client to be able to get to it multiple ways. It seems like that should be possible I just can't figure out how to do it.
In case anyone wants to know, the reason I need this is because there are some things on the main front end with end user access that need to get the data (in this case the customer account) and there is also some things on my back end server (payment gateway and processing etc.) that need to access the same data but should do it via API key.
I want the same endpoint, I just want the client to be able to get to it multiple ways. It seems like that should be possible I just can't figure out how to do it.
Well, it would be nicer if you could have shared your existing implementation. However, according to your scenario and requirement, you could achieve a single endpoint accepting both API keys and bearer tokens in ASP.NET Core by creating a custom authentication scheme that handles both verification and claim generation for each method.
In order to implement that, create a custom authentication scheme and define an authorization policy requiring authentication through it.
Finally, decorate your controller actions with the policy to enforce access control based on valid API keys or tokens.
Let's have a look in practice how we could achieve that:
First, define separate authentication schemes for API key and bearer token authentication. For instance, you can do as following:
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public string ApiKey { get; set; }
}
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("ApiKey", out var apiKeyValues))
{
return AuthenticateResult.Fail("Missing API Key");
}
var providedApiKey = apiKeyValues.FirstOrDefault();
var expectedApiKey = Options.ApiKey;
if (string.IsNullOrEmpty(providedApiKey) || providedApiKey != expectedApiKey)
{
return AuthenticateResult.Fail("Invalid API Key");
}
var claims = new[] { new Claim(ClaimTypes.Name, "APIKeyUser") };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Now, create a custom authentication policy that combines both authentication schemes.
Program.cs File:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "your_issuer",
ValidAudience = "your_audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key"))
};
});
builder.Services.AddAuthentication("ApiKey")
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKey", null);
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiKeyOrBearer", policy =>
{
policy.AddAuthenticationSchemes("Bearer", "ApiKey");
policy.RequireAuthenticatedUser();
});
});
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Note: In program.cs file you should have token configuration and the custom policy. This approach avoids duplicate endpoints and simplifies client interaction, providing a unified authentication mechanism for both methods. So,now apply this custom authentication policy to the endpoint you want to protect.
For example: Let't say you have following controller:
[HttpGet]
[Authorize("ApiKeyOrBearer")]
public IActionResult Get()
{
return Ok(new { message = "Authorized" });
}
Now, you can use both your api key and token for accessing above contorller.
Note: Please refer to this official document for more information.