ASP.NET core - simple API key authentication - c#

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.

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

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

Related

How to access HttpContext and Request in RequireAssersion?

I'm trying to create a custom authorization policy. Let's say I want the URL to contain a token. For example:
https://example.com/customer/list?token=2nHxltsDOjThQJWufcGU1v36RdqYoBE9
I want to show the list of customer to the user, only if:
URL has a token query string
token is valid
I tried this:
services.AddAuthorization(options =>
{
options.AddPolicy("IsPaydar", policy => policy.RequireAssertion(x => // stuck here))
});
But I don't see how can I access HttpContext or the Request object from inside the policy.RequireAssertion.
How can I implement this?
You can create a custom Authorization Handler, and in this handler get the token parameter from the Query string, and valid the joken. Refer the following steps:
Create a JwtTokenRequirement class: here we can set the query string parameter key value. then in the handler method, we could according to it to find the token.
//required using Microsoft.AspNetCore.Authorization;
public class JwtTokenRequirement : IAuthorizationRequirement
{
public JwtTokenRequirement(string tokenname)
{
TokenName = tokenname;
}
public string TokenName { get; set; }
}
Create the JWTTokenHandler:
//required using Microsoft.AspNetCore.Authorization;
//required using Microsoft.AspNetCore.Http;
public class JWTTokenHandler : AuthorizationHandler<JwtTokenRequirement>
{
IHttpContextAccessor _httpContextAccessor = null;
public JWTTokenHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, JwtTokenRequirement requirement)
{
HttpContext httpContext = _httpContextAccessor.HttpContext; // Access context here
var token = httpContext.Request.Query[requirement.TokenName].ToString(); //get the jwtkoken
if (!string.IsNullOrEmpty(token))
{
if (IsValidToken(token))
{
context.Succeed(requirement);
}
}
else
{
context.Fail();
}
return Task.FromResult(0);
}
public bool IsValidToken(string authToken)
{
//validate Token here
return true;
}
}
Add the following code to the ConfigureServices method:
services.AddHttpContextAccessor();
services.AddAuthorization(options =>
{
options.AddPolicy("jwtpolicy",
policy => policy.Requirements.Add(new JwtTokenRequirement("jwttoken"))); //configure the policy and set the parameter key value. in this sample the key is "jwttoken"
});
services.AddSingleton<IAuthorizationHandler, JWTTokenHandler>();
Apply the Authorize attribute on the API action method:
[Authorize(Policy = "jwtpolicy")]
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
Then the result is like this:
Since the question was specifically about RequireAssertion, which takes a Func, which receives an AuthorizationHandlerContext, the answer is that the HttpContext is the AuthorizationHandlerContext’s Resource. To use OP’s code:
services.AddAuthorization(options =>
{
options.AddPolicy("IsPaydar", policy
=> policy.RequireAssertion(x => (x.Resource as HttpContext)?.Request?.Query["token"] == "myCoolToken")
});
Whether the Resource property really contains the HttpContext is up to the framework, so Zhi Lv’s solution is certainly going to be more robust and you should probably use it anyway if your validation logic exceeds one line.

Load JWT into User.Identity automatically (ASP.NET Core 3.1)

I am working on a project with micro-services's architecture. I have one API Rest that works as an API Gateway where I want to get the username from a JSON Web Token. It may be important to note that the authentication and authorization of the system are being dealt with on another part of the system (I just need to get the username).
So far I was able to get the username from the JWT using this extension of HttpContext:
public static class HttpContextExtensions
{
private static JwtSecurityToken GetJwt(this HttpContext httpContext)
{
bool existeToken = httpContext.Request.Headers.TryGetValue("Authorization", out StringValues authorizationValue);
if (!existeToken)
{
return null;
}
string encodedToken = authorizationValue.FirstOrDefault().Split(" ", 2)[1];
return new JwtSecurityTokenHandler().ReadJwtToken(encodedToken);
}
public static string GetUsername(this HttpContext httpContext)
{
JwtSecurityToken jwt = httpContext.GetJwt();
Claim usernameClaim = jwt.Claims.FirstOrDefault(c => c.Type == "preferred_username");
return usernameClaim?.Value;
}
}
And then I can get the username on the controller:
string username = HttpContext.GetUsername();
I was wondering if there is a more elegant way to do this on .Net Core 3.1. I tried to configure my project so I can get all the Claims loaded into User.Identity in my Controller's Action, but I failed miserably. All the documentation I found was about previous versions of .Net Core.
I think I may be doing something wrong on the Startup.cs. If anyone can point me to the right direction I would really appreciate it.
I did an approach by filling the User on HttpContext in a middleware:
public class AuthMiddleware
{
private readonly RequestDelegate _next;
public AuthMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null)
{
var jwtEncodedString = authHeader.Substring(7);
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
var identity = new ClaimsIdentity(token.Claims, "basic");
context.User = new ClaimsPrincipal(identity);
}
return _next(context);
}
}
Then on app setup;
public static IApplicationBuilder UseApiConfiguration(this IApplicationBuilder app, IWebHostEnvironment env)
{
// add middleware reference
app.UseMiddleware<AuthMiddleware>();
return app;
}

How do we log authentication token requests in ASP.NET Web API

We have an ASP.NET Web Api application which uses OAuth Bearer Tokens for authentication, for which we are trying to implement Request/Response logging.
Basically it works like this:
1. User sends request to "/authenticate" and receives an authentication token
2. User then uses this authentication token for requests to the exposed API methods
For logging requests to the exposed API methods, we use a DelegatingHandler which works perfectly fine.
However, requests made to "/authenticate" are not captured by the DelegatingHandler implementation.
Is there a different approach required for logging requests for tokens?
public abstract class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var correlationId = Guid.NewGuid();
var requestInfo = string.Format("{0} {1}", request.Method, request.RequestUri);
var requestContent = await request.Content.ReadAsByteArrayAsync();
var context = ((HttpContextBase)request.Properties["MS_HttpContext"]);
await IncomingMessageAsync(correlationId, request.Method, request.RequestUri, request.Headers, requestContent,
context.Request.UserHostAddress, context.Request.IsAuthenticated, context.User.Identity.Name);
var response = await base.SendAsync(request, cancellationToken);
byte[] responseMessage;
responseMessage = await response.Content.ReadAsByteArrayAsync();
await OutgoingMessageAsync(correlationId, response.StatusCode, response.Headers, responseMessage);
return response;
}
protected abstract Task IncomingMessageAsync(Guid correlationId, HttpMethod requestMethod, Uri requestUri, HttpRequestHeaders requestHeaders, byte[] messageContent, string ipAddress, bool isAuthenticated, string requestMadeByUserName);
protected abstract Task OutgoingMessageAsync(Guid correlationId, HttpStatusCode statusCode, HttpResponseHeaders responseHeaders, byte[] messageContent);
}
EDIT w/ OAuth Code
[assembly: OwinStartup(typeof(MyApp.Infrastructure.IdentityConfig))]
namespace MyApp.Infrastructure
{
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationIdentityDbContext>(() => ApplicationIdentityDbContext.Create(ConfigurationDataProvider.MYDBCONNSTRING));
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
Provider = new ApplicationAuthProvider(),
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Authenticate")
});
}
}
}
You are installing OWIN middleware to issue tokens before the WebAPI middleware.
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
Provider = new ApplicationAuthProvider(),
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Authenticate")
});
The DelegatingHandler you try to use to log the request is part of the Web API middeware and is never reached because the token issuing middleware handles the request and does not call middleware further in the pipeline.
Instead of using DelegatingHandler, use the following middleware and install it before the token middleware.
public class RequestLoggerMiddleware
{
private readonly Func<IDictionary<string, object>, Task> _next;
private readonly ILogger _logger;
public RequestLoggerMiddleware(
Func<IDictionary<string, object>, Task> next,
ILogger logger)
{
_next = next;
_logger = logger;
}
public Task Invoke(IDictionary<string, object> environment)
{
var context = new OwinContext(environment);
_logger.Write($"{context.Request.Method} {context.Request.Uri.AbsoluteUri}");
var result = _next.Invoke(environment);
_logger.Write($"Status code: {context.Response.StatusCode}");
return result;
}
}
To install the middleware, just insert the statement: app.Use(typeof (RequestLoggerMiddleware)); before the app.UseOAuthBearerTokens statement in your Startup.cs.

Retrieve access token from a customised header

In my Web API, I want to get the access token from the Cookies header in the request, and then do the validation on the token. At the moment, IdentityServer3.AccessTokenValidation package is used to do the validation of the Bearer token and it looks for the token from the Authorization header only. Preferably I would like to keep using the same bearer token validation process, but getting the token from the Cookies header, does that sound doable with handy codes? Thanks
Just implement your own TokenProvider and provide it to the AccessTokenValidationMiddleware:
public class MyCustomTokenProvider : IOAuthBearerAuthenticationProvider
{
public Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
//try get from cookie
var tokenCookie = context.Request.Cookies["myCookieName"];
if (tokenCookie != null)
{
context.Token = tokenCookie;
}
}
return Task.FromResult(0);
}
public Task ValidateIdentity(OAuthValidateIdentityContext context)
{
throw new NotImplementedException();
}
public Task ApplyChallenge(OAuthChallengeContext context)
{
throw new NotImplementedException();
}
}
In your Startup.cs:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://myhost",
RequiredScopes = new[] { "my-scope" },
TokenProvider = new MyCustomTokenProvider()
});

Return HTTP 403 using Authorize attribute in ASP.Net Core

When using ASP.Net WebAPI, I used to have a custom Authorize attribute I would use to return either an HTTP 403 or 401 depending on the situation. e.g. if the user is not authenticated, return a 401; if the user is authenticated but doesn't have the appropriate permissions, return a 403. See here for more discussion on that.
It seems now, in the new ASP.Net Core, they don't want you overriding the Authorize attribute anymore instead favoring a policy-based approach. However, it seems Core MVC suffers from the same "just return 401 for all auth errors" approach its predecessors have.
How do I override the framework to get the behavior I want?
After opening an issue here, it appears this actually should work...sort of.
In your Startup.Configure, if you just call app.UseMvc() and don't register any other middleware, you will get 401 for any auth-related errors (not authenticated, authenticated but no permission).
If, however, you register one of the authentication middlewares that support it, you will correctly get 401 for unauthenticated and 403 for no permissions. For me, I used the JwtBearerMiddleware which allows authentication via a JSON Web Token. The key part is to set the AutomaticChallenge option when creating the middleware:
in Startup.Configure:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
app.UseMvc();
AutomaticAuthenticate will set the ClaimsPrincipal automatically so you can access User in a controller. AutomaticChallenge allows the auth middleware to modify the response when auth errors happen (in this case setting 401 or 403 appropriately).
If you have your own authentication scheme to implement, you would inherit from AuthenticationMiddleware and AuthenticationHandler similar to how the JWT implementation works.
I ended up doing it with middleware:
public class AuthorizeCorrectlyMiddleware
{
readonly RequestDelegate next;
public AuthorizeCorrectlyMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
if (context.User.Identity.IsAuthenticated)
{
//the user is authenticated, yet we are returning a 401
//let's return a 403 instead
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}
which should be registered in Startup.Configure before calling app.UseMvc().
I followed the guide for Custom Authorization Policy Providers using IAuthorizationPolicyProvider in ASP.NET Core and also wanted to create a custom response.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-5.0
The guide I followed for that was Customize the behavior of AuthorizationMiddleware
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0
My code finally looked like this:
public class GuidKeyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler
DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(
RequestDelegate requestDelegate,
HttpContext httpContext,
AuthorizationPolicy authorizationPolicy,
PolicyAuthorizationResult policyAuthorizationResult)
{
if (policyAuthorizationResult.Challenged && !policyAuthorizationResult.Succeeded && authorizationPolicy.Requirements.Any(requirement => requirement is GuidKeyRequirement))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
policyAuthorizationResult);
}
}
Startup.cs:
services.AddSingleton<IAuthorizationMiddlewareResultHandler,
GuidKeyAuthorizationMiddlewareResultHandler>();
You can also edit your AuthorizationHandler and access httpContext
via IHttpContextAccessor. However this feels more like a hack.
internal class GuidKeyAuthorizationHandler : AuthorizationHandler<GuidKeyRequirement>
{
private readonly ILogger<GuidKeyAuthorizationHandler> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public GuidKeyAuthorizationHandler(ILogger<GuidKeyAuthorizationHandler> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
// Check whether a given GuidKeyRequirement is satisfied or not for a particular context
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, GuidKeyRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext; // Access context here
var key = System.Web.HttpUtility.ParseQueryString(httpContext.Request.QueryString.Value).Get("key");
if (!string.IsNullOrWhiteSpace(key))
{
// If the user guid key matches mark the authorization requirement succeeded
if (Guid.TryParse(key, out var guidKey) && guidKey == requirement.Key)
{
_logger.LogInformation("Guid key is correct");
if (requirement.RequireRefererHeader)
{
_logger.LogInformation("Require correct referer header");
httpContext.Request.Headers.TryGetValue("Referer", out var refererHeader);
if (requirement.RefererHeader == refererHeader)
{
_logger.LogInformation("Referer header is correct");
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
_logger.LogInformation($"Referer header {refererHeader} is not correct");
}
}
else
{
_logger.LogInformation("Correct referer header is not needed");
context.Succeed(requirement);
return Task.CompletedTask;
}
}
else
{
_logger.LogInformation($"Guid key {guidKey} is not correct");
}
}
else
{
_logger.LogInformation("No guid key present");
}
var msg = "Invalid Guid";
var bytes = Encoding.UTF8.GetBytes(msg);
httpContext.Response.StatusCode = 403;
httpContext.Response.ContentType = "application/json";
httpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
return Task.CompletedTask;
}
}
Found that solution here:
https://stackoverflow.com/a/61861098/3850405

Categories

Resources