MinimalAPI Action filter never get executed - c#

I am working on a minimal API and I am trying to make an actionfilter for authentication works with it.
I tried to add my action filter directly on the endpoint registration likeso:
app.MapGet("/Users/{userId}", [ServiceFilter(typeof(CustomAuthorizationAttribute))] async (IMediator mediator, string userId) =>
{
//SomeCode
});
I also tried with the attribute directly.
app.MapGet("/Users/{userId}", [CustomAuthorizationAttribute()] async (IMediator mediator, string userId) =>
{
//SomeCode
});
At first I tried to make an extension to add it in a more elegant way and it didn't work either.
public static TBuilder AddCustomAuthorizationAttribute<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Add(endpointBuilder =>
{
endpointBuilder.Metadata.Add(new CustomAuthorizationAttribute());
});
return builder;
}
There is my simple ActionFilter. I am placing my breakpoint under the onActionExecuting code to intercept when the attribute get called but it never get called.
public class CustomAuthorizationAttribute : ActionFilterAttribute, IAsyncActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context); //BreakPoint
}
public override Task OnActionExecutionAsync(ActionExecutingContext context,ActionExecutionDelegate actionExecutionDelegate)
{
return base.OnActionExecutionAsync(context, actionExecutionDelegate); //BreakPoint
}
}

Action filters are part of ASP.NET Core MVC pipeline and are not applicable (at least at the moment of writing) to Minimal APIs.
UPD
Starting .NET 7 Minimal API's will have similar feature allowing to use AddFilter method. See also issue1 issue2, PR, and IRouteHandlerFilter interface.

Related

Custom Authorization Filtering Minimal API .Net 6

I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.

ActionFilter equivalent in Asp.net Core Razor Pages

First off all,I know there is no Action Filter in asp.net core razor pages.But I am searching to find a mechanism that works similar.
What I am trying to achieve is to apply filter based on handler methods.When I try to use pagefilter, it is applied to all methods.Is there any method/way to exclude some handler methods on the same page ?
To clarify question I added some examples.
Here is example pagefilter
public class FormValidatorRazor : IAsyncPageFilter
{
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HttpContext.Request.Method.Equals("POST") || context.HttpContext.Request.Method.Equals("PUT"))
//code removed for brevity
}
Here is how I apply it to the project
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages().AddMvcOptions(options =>
{
options.Filters.Add(new FormValidatorRazor());
//code removed for brevity
}
}
Here is example handler methods.
public async Task<IActionResult> OnPostUpdate(ExModel model)
{
}
public async Task<IActionResult> OnPostEdit(ExModel model)
{
}
I want my filter to be applied to OnPostUpdate but not to OnPostEdit.How can I achieve that behaviour ?
The PageHandlerExecutingContext parameter passed in to OnPageHandlerExecutionAsync provides everything you need to achieve this. Here's an example that shows the specifics:
public async Task OnPageHandlerExecutionAsync(
PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context.HandlerMethod?.Name == "Update")
{
// Running inside a handler method named "Update".
}
// ...
}
HandlerMethod is non-null when a handler method is being executed. It provides these properties, that might be of interest:
HttpMethod, e.g. PUT, POST
Name, e.g. Edit, Update
MethodInfo
Alternatively, if you'd like to opt-out at the handler level, you could use a custom attribute and check for its existence in OnPageHandlerExecutionAsync:
public class SomePageFilterExcludeAttribute : Attribute { }
[SomePageFilterExclude]
public void OnPostUpdate() { }
public async Task OnPageHandlerExecutionAsync(
PageHandlerExecutingContext ctx, PageHandlerExecutionDelegate next)
{
var isHandlerExcluded = ctx.HandlerMethod?.MethodInfo?.
GetCustomAttributes(typeof(SomePageFilterExcludeAttribute), false).Any() == true;
// ...
}

ASP.NET 5 Policy-Based Authorization Handle Not Being Called

Following the docs here I tried to implement a policy-based auth scheme. http://docs.asp.net/en/latest/security/authorization/policies.html#security-authorization-handler-example
I ran into the issue that my Handle method was not being called on my custom AuthorizationHandler. (It does not throw here). It also does inject the dependency currently in the constructor.
Here it the AuthorizationHandler Code.
using WebAPIApplication.Services;
using Microsoft.AspNet.Authorization;
namespace WebAPIApplication.Auth
{
public class TokenAuthHandler : AuthorizationHandler<TokenRequirement>, IAuthorizationRequirement
{
private IAuthService _authService;
public TokenAuthHandler(IAuthService authService)
{
_authService = authService;
}
protected override void Handle(AuthorizationContext context, TokenRequirement requirement)
{
throw new Exception("Handle Reached");
}
}
public class TokenRequirement : IAuthorizationRequirement
{
public TokenRequirement()
{
}
}
}
In Start Up I have
// Authorization
services.AddSingleton<IAuthorizationHandler, TokenAuthHandler>()
.AddAuthorization(options =>
{
options.AddPolicy("ValidToken",
policy => policy.Requirements.Add(new TokenRequirement()));
});
The controller method is
// GET: api/values
[HttpGet, Authorize(Policy="ValidToken")]
public string Get()
{
return "test";
}
Hitting this endpoint returns nothing and there is a warning in the console of
warn: Microsoft.AspNet.Mvc.Controllers.ControllerActionInvoker[0]
Authorization failed for the request at filter 'Microsoft.AspNet.Mvc.Filters.AuthorizeFilter'.
I am able to hit other endpoints that don't have the attribute successfully.
SOS,
Jack
I'm putting this here for reference because I spent way too long figuring this out...
I had implemented a custom requirement and handler (empty for testing's sake):
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
public class TestHandler : AuthorizationHandler<TestRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class TestRequirement : IAuthorizationRequirement
{
}
Registered it in my Startup.cs ConfigureServices() section:
services.AddAuthorization(options =>
{
options.AddPolicy("Test", policy => policy.Requirements.Add(new TestRequirement()));
// Other policies here
}
Added it to my controller method:
[HttpGet]
[Authorize(Policy = "Test")]
public IActionResult Index()
{
Return View();
}
But was getting a 403 error (not 401) with every request to the controller method!
Turns out, I was not registering TestHandler with the ConfigureServices() (Dependency Injection) section of Startup.cs.
services.AddSingleton<IAuthorizationHandler, TestHandler>();
Hope this saves someone from banging their head on their desk. :|
The answer to this question is alluded to in a comment to adem caglin, so props to him.
The issue is that the AuthorizeFilter is rejecting the request before the AuthorizationHandler is being called. This is because for every use of the Authorize tag MVC adds AuthorizeFilter ahead of the AuthorizationHandler in the pipeline. This AuthorizeFilter checks to see if any of the current users identities are authorized. In my case there were no authorized identities associated with any user so this would always fail.
A solution (which IMO is somewhat hackish) is to insert a peice of middleware that will get executed before any MVC code. This middleware will add a generic authenticated identity to a User (if the user does not already have one).
Consequently the AuthorizeFilter check will pass and the Handle method on the AuthenticationHandler method will be executed and our problem will be solved. The middleware code (which needs to be added to Configure before app.UseMvc(); is called) is as follows
app.Use(async (context, next) =>
{
if (!context.User.Identities.Any(i => i.IsAuthenticated))
{
context.User = new ClaimsPrincipal(new GenericIdentity("Unknown"));
}
await next.Invoke();
});
An alternative way to override the AuthorizeFilter is outline here (Override global authorize filter in ASP.NET Core MVC 1.0)
Citing the response from here (Asp.Net Core policy based authorization ends with 401 Unauthorized)
Take a look at Asp.net Core Authorize Redirection Not Happening i think adding options.AutomaticChallenge = true; solves your problem.

Is there a way to create an ActionFilter that wraps the content of the Action in a using statement?

My scenario: My application is a Web Api 2 app using a business logic and repository layer for data access. The web application uses ASP.NET Impersonation to login to the database as the user accessing the website (authenticated via PKI). I have several async controller methods. However, when I await on the data access methods, the database call may complete on a different thread which will then access the database under the identity of my application pool which is not allowed to connect to the database.
Example Controller:
public class TestApiController : ApiController {
private IBusinessLogicObject _myBlObject;
public TestApiController(IBusinessLogicObject myBlObject){
_myBlObject = myBlObject; //Populated through Unity
}
public async Task<int> CountMyJobs(string name){
return await _myBlObject.CountMyJobsAsync(name);
}
}
Example Business Logic Class:
public class BusinessLogicObject : IBusinessLogicObject
{
private IGenericRepository<Job> _jobRepository;
public BusinessLogicObject(IGenericRepository<Job> _jobRepository)
{
_jobRepository = jobRepository; //Populated with Unity
}
public Task<int> CountMyJobsAsync(string name)
{
using (WindowsIdentity.GetCurrent().Impersonate())
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
}
}
If I move the using statement into the controller (wrapped around the await), it works fine.
The issue seems to be that because the await is outside of the impersonation context, it does not impersonate the database call (the CountAsync()) and I am unable to open a connection to my database.
The Question:
Is there a way I could write an ActionFilter or some other attribute on my controller method so that the method itself (containing the await call) would be automatically wrapped in the using statement?
merpmerp's answer is going to be problematic in multi-threaded servers. Since there is only one instance of an ActionFilterAttribute per decorated method, two simultaneous requests to the same method will result in usingVariable being overwritten, and only one will end up being disposed.
You'll need to take this a step further and store the ImpersonationContext somewhere in the request context-- e.g. in filterContext.Request.Properties.
I don't believe there is a way to actually wrap a method in a using statement with an attribute, but you could essentially do the same thing by using the OnActionExecuting and OnResultExecuted methods in a custom ActionFilter.
public class IdentityImpersonateActionFilter : ActionFilterAttribute
{
IDisposable usingVaribale;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
usingVaribale.Dispose();
}
}
Then you could decorate your methods or your whole controller class with [IdentityImpersonate]
[IdentityImpersonate]
public Task<int> CountMyJobsAsync(string name)
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
You could also access this using variable in your function if you wish
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
filterContext.ActionParameters.Add("parameterName", usingVaribale);
}
And add the parameter to your controller function
[IdentityImpersonate]
public Task<int> CountMyJobsAsync(object parameterName, string name)
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
Hope this helps!
If you want to keep the impersonation the responsibility of the business logic, then you can just do this:
public async Task<int> CountMyJobsAsync(string name)
{
using (WindowsIdentity.GetCurrent().Impersonate())
{
return await _jobRepository.Where(i => i.Name == name).CountAsync()
.ConfigureAwait(false);
}
}

Add custom header to all responses in Web API

Simple question, and I am sure it has a simple answer but I can't find it.
I am using WebAPI and I would like to send back a custom header to all responses (server date/time requested by a dev for syncing purposes).
I am currently struggling to find a clear example of how, in one place (via the global.asax or another central location) I can get a custom header to appear for all responses.
Answer accepted, here is my filter (pretty much the same) and the line i added to the Register function of the WebApi config.
NOTE: The DateTime stuff is NodaTime, no real reason just was interested in looking at it.
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Content.Headers.Add("ServerTime", Instant.FromDateTimeUtc(DateTime.Now.ToUniversalTime()).ToString());
}
Config Line:
config.Filters.Add(new ServerTimeHeaderFilter());
For that you can use a custom ActionFilter (System.Web.Http.Filters)
public class AddCustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add("customHeader", "custom value date time");
}
}
You can then apply the filter to all your controller's actions by adding this in the configuration in Global.asax for example :
GlobalConfiguration.Configuration.Filters.Add(new AddCustomHeaderFilter());
You can also apply the filter attribute to the action that you want without the global cofiguration line.
Previous answers to this question don't address what to do if your controller action throws an exception. There are two basic ways to get that to work:
Add an exception filter:
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
public class HeaderAdderExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Response == null)
context.Response = context.Request.CreateErrorResponse(
HttpStatusCode.InternalServerError, context.Exception);
context.Response.Content.Headers.Add("header", "value");
}
}
and in your WebApi setup:
configuration.Filters.Add(new HeaderAdderExceptionFilter());
This approach works because WebApi's default exception handler will send the HttpResponseMessage created in a filter instead of building its own.
Replace the default exception handler:
using System.Net;
using System.Net.Http;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Results;
public class HeaderAdderExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
HttpResponseMessage response = context.Request.CreateErrorResponse(
HttpStatusCode.InternalServerError, context.Exception);
response.Headers.Add("header", "value");
context.Result = new ResponseMessageResult(response);
}
}
and in your WebApi setup:
configuration.Services.Replace(typeof(IExceptionHandler), new HeaderAdderExceptionHandler());
You can't use both of these together. Okay, well, you can, but the handler will never do anything because the filter already converted the exception into a response.
Super important to note that as written, this code will send all the exception details to the client. You probably don't want to do this in production, so check out all the available overloads on CreateErrorResponse() and pick which one suits your needs.
Julian's answer led me to have to create the filter but only using the the System.Web (v4) and System.Web.Http (v5) namespace (MVC packages were not part of this particular project this was used on.)
using System.Web;
using System.Web.Http.Filters;
...
public class AddCustomHeaderActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
actionExecutedContext.ActionContext.Response.Headers.Add("name", "value");
}
}
And add it to the global.asax to have it used on every controller/action
GlobalConfiguration.Configuration.Filters.Add(new AddCustomHeaderActionFilterAttribute());
Neither of the above two solutions worked for me. They wouldn't even compile. Here's what I did. Added:
filters.Add(new AddCustomHeaderFilter());
to RegisterGlobalFilters(GlobalFilterCollection filters) method in FiltersConfig.cs and then added
public class AddCustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
actionExecutedContext.HttpContext.Response.Headers.Add("ServerTime", DateTime.Now.ToString());
}
}
It can be done by the messagehandler easily, it will handle both ok response and exception case.
public class CustomHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// add header to request if you want
var response = await base.SendAsync(request, cancellationToken);
response.Headers.Add("cutomKey", "cutomValue");
return response;
}
}
Add it in the config
config.MessageHandlers.Add(new CustomHeaderHandler());
According to my requirement, below single line of code serves the purpose.
System.Web.HttpContext.Current.Response.Headers.Add("Key", "Value")
I combined the normal and exception path in one class:
public class CustomHeaderAttribute : FilterAttribute, IActionFilter, IExceptionFilter
{
private static string HEADER_KEY { get { return "X-CustomHeader"; } }
private static string HEADER_VALUE { get { return "Custom header value"; } }
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
return (new CustomHeaderAction() as IActionFilter).ExecuteActionFilterAsync(actionContext, cancellationToken, continuation);
}
public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
return (new CustomHeaderException() as IExceptionFilter).ExecuteExceptionFilterAsync(actionExecutedContext, cancellationToken);
}
private class CustomHeaderAction: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
{
actionExecutedContext.Response.Content.Headers.Add(HEADER_KEY, HEADER_VALUE);
}
}
}
private class CustomHeaderException : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Response == null)
{
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, context.Exception);
}
context.Response.Content.Headers.Add(HEADER_KEY, HEADER_VALUE);
}
}
}
Nothing fancy but at least it gives me one place to control my additional headers. For now it's just static content but you could always hook it up to some sort of dictionary generator/factory.
I had the same problem while trying to add a new header to the whole controller, just add "services.AddHttpContextAccessor();" to startup.cs then create your controller
public class EnController : Controller{
public EnController(IHttpContextAccessor myHttpAccessor)
{
myHttpAccessor.HttpContext.Response.Headers.Add("Content-Language", "en-US");
}
... more methods here...
}

Categories

Resources