I have a custom filter:
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//do something
}
}
In the Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
The above codes will be called everytime an action is invoked.
However, in my _Layout.cshtml, I have 2 different "BaseController", something like:
#Html.Action("SomeAction", "Base")
#Html.Action("SomeAction", "Base2")
When I set a break point, it appears that the SetAuthFilter is always being called three times, first before the page was launched, then second time when my break point hits the BaseController, then finally the third time when my break point hits the Base2Controller.
What should I do in order to avoid SetAuthFilter being called multiple times?
You simply cannot prevent it from being called if there are multiple actions that are interacting with the filter. It will be called every single request. However, you could cache your last request for that user's identity and, if it is the same request, immediately return without continuing onto the heavier authorization checks.
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = CreateKey(filterContext);
var isCached = HttpRuntime.Cache.Get(key);
if (isCached != null) return;
HttpRuntime.Cache.Insert(key, true);
// Heavy auth logic here
}
private string CreateKey(AuthorizationContext context)
{
// Or create some other unique key that allows you to identify
// the same request
return
context.RequestContext.HttpContext.User.Identity.Name + "|" +
context.RequestContext.HttpContext.Request.Url.AbsoluteUri;
}
}
Note that this doesn't account for null identities or bad URIs. You'd want to add some additional defensive checks as well as a cache invalidation strategy.
This will not allow you to bypass your authentication, since each unique request will still need to be validated. However, it minimizes the number of times you call expensive authorization logic.
For every secure controller action, the OnAuthorization overload will get called.
If you dont want that to happen, you should decorate your function with AllowAnonymous attribute.
If you don't want to call custom filter on each method:
Then remove the following line from Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
Add [SetAuth] attribute as follows on those methods and Controllers which really needs authorization filter :
[SetAuth]
public ActionResult Index()
{
// your code
return View(yourModel);
}
Related
I want to identify the one point which is hit every time before a request goes to the controller in the webAPI. I need to put in a custom authentication at that point. I am already doing a customAuthorization but I want to tweak in some custom authentication even before it reaches the controller.
The application_Start method only gets triggered once and so I am not quite sure what is the one place where the control goes every time we put in a URL in the browser and hit enter.
Gloabal.asax has more methods, which can be overloaded and one of them is Application_BeginRequest
And here's more detailed lifecycle. Controller factory also might help you intercepting and tweeking requests.
protected void Application_BeginRequest(object sender, EventArgs e) //Not triggered with PUT
{
//your code
}
You can opt for ActionFilterAttribute of Web API. This is triggered for every request that comes in.
Execution pipeline:
Controller Constructor > ActionFilter's OnActionExecuting > Controller action > ActionFilter's OnActionExecuted
Simple ActionFilterAttribute implementation:
public class YourFilterName : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// pre-processing
//Your authentication logic goes here - use actionContext
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
if (objectContent != null)
{
var type = objectContent.ObjectType; //type of the returned object
var value = objectContent.Value; //holding the returned value
}
Debug.WriteLine("OnActionExecuted Response " + actionExecutedContext.Response.StatusCode.ToString());
}
}
I am building a ASP.NET Core MVC application and am trying to create a global action filter that logs how much time is spent executing an action (it should only log if spent time is above some threshold). I have succesfully done this but now I want to be able to say that a single action or a single controller should have a different threshold. When I try this, my action filter is applied twice(which is not what I want) but with the correct two different thresholds.
I have tried quite a few things and searched around. In an MVC 3 and an MVC 4 project I have successfully done this using RegisterGlobalFilters() in Global.asax and it automatically overrides the global one when I used the attribute on a controller/action. I have also tried the approach listed in this post, without luck:
Override global authorize filter in ASP.NET Core MVC 1.0
My code for my ActionFilterAttribute:
public class PerformanceLoggingAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1; // Log everything unless this is explicitly set
private Stopwatch sw;
public override void OnActionExecuting(ActionExecutingContext context)
{
sw = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
// Log here
}
}
//public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
//{
// // If there is another performance filter, do nothing
// if (context.Filters.Any(item => item is PerformanceLoggingAttribute && item != this))
// {
// return Task.FromResult(0);
// }
// return base.OnActionExecutionAsync(context, next);
//}
}
I am applying this global filter in my Startup.cs:
services.AddMvc(options =>
{
if (_env.IsProduction()) options.Filters.Add(new RequireHttpsAttribute());
//options.Filters.Add(new PerformanceLoggingFilter() { ExpectedMax = 1 }); // Add Performance Logging filter
options.Filters.Add(new PerformanceLoggingAttribute() { ExpectedMax = 1 }); // Add Performance Logging filter
});
And in my controller I am applying the attribute:
//[TypeFilter(typeof(PerformanceLoggingFilter))]
[PerformanceLogging(ExpectedMax = 2)]
public IActionResult Index()
{
var vm = _performanceBuilder.BuildPerformanceViewModel();
return View(vm);
}
As you can tell from the code snippets above I have tried the OnActionExecutionAsync approach and I have also tried a IActionFilter instead and using [TypeFilter(typeof(PerformanceLoggingFilter))] on actions, but no luck.
Can anyone help me out?
May suggest you a bit different implementation of what you try to achieve by using one action filter and additional custom attribute:
create a new simple attribute (let's name it ExpectedMaxAttribute), that just holds the ExpectedMax value. Apply this attribute to controller's actions with different values.
keep your PerformanceLogging action filter as global, but modify implementation. On OnActionExecuted method check if controller's action has ExpectedMaxAttribute. If yes, then read ExpectedMax value from attribute, otherwise use the default value from the action filter.
Also, I recommend you to rename action filter accordingly to convention naming something like PerformanceLoggingActionFilter.
I got it working thanks to #Set's answer above in combination with this answer:
https://stackoverflow.com/a/36932793/5762645
I ended up with a global action that is applied to all actions and then having a simple ExpectedMaxAttribute that I put on actions where the threshold should be different. In the OnActionExecuted of my global action filter, I then check if the action in question has the ExpectedMaxAttribute attached to it and then read the ExpectedMax from that. Below is my attribute:
public class PerformanceLoggingExpectedMaxAttribute : ActionFilterAttribute
{
public int ExpectedMax = -1;
}
And the OnActionExecuted part that I added to my ActionFilter:
public override void OnActionExecuted(ActionExecutedContext context)
{
sw.Stop();
foreach (var filterDescriptor in context.ActionDescriptor.FilterDescriptors)
{
if (filterDescriptor.Filter is PerformanceLoggingExpectedMaxAttribute)
{
var expectedMaxAttribute = filterDescriptor.Filter as PerformanceLoggingExpectedMaxAttribute;
if (expectedMaxAttribute != null) ExpectedMax = expectedMaxAttribute.ExpectedMax;
break;
}
}
if (sw.ElapsedMilliseconds >= ExpectedMax)
{
_logger.LogInformation("Test log from PerformanceLoggingActionFilter");
}
}
I created a custom authorization filter with some checks in it. When the check fails it is writing to a log file. The strange thing is that with every fail it writes the error text twice to the log. How to make sure it only logs the error once?
public class AuthorizationFilter : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = “wrong key”;
if (key != “correct key”)
{
DateTime DateTime = filterContext.HttpContext.Timestamp;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Logs\log.txt");
using (StreamWriter sw = File.AppendText(path))
{
sw.WriteLine(DateTime + “| error XYZ”);
}
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
Assuming you have the filter registered globally...
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizationFilter());
filters.Add(new HandleErrorAttribute());
}
}
It will fire once when the original action is run. Then it will return 401 unauthorized. This status is caught by ASP.NET and will automatically redirect to the login page. When the login page loads, your filter runs again (and presumably fails again).
To make it stop doing this, there are a couple of options.
Inherit from AuthorizeAttribute instead of FilterAttribute, IAuthorizationFilter. Override the AuthorizeCore method and return false when the login fails. Use the AllowAnonymousAttribute attribute on your login method (and any other methods you don't want to check).
Build your own logic to either check for AllowAnonymousAttribute or a custom attribute. Here is an example of checking for an attribute within a filter.
I suggest you use the first option. The reason is that in addition to automatically gaining the functionality of the AllowAnonymousAttribute there is also some code to deal with using output caching in conjunction with authorization.
I'm trying to find a solution for implementing custom System.Web.Mvc.AuthorizeAttribute by deriving from it and overriding some of its methods.
Every approach I'm trying, I'm facing with certain issues in the default authorization mechanism of the MVC 5 that prevents me from proper extending that.
I've done the huge research on this field on SO and many dedicated resources, but I couldn't get a solid solution for the scenario like my current one.
First limitation:
My authorization logic needs additional data like controller and method names and attributes applied to them rather than limited portion of the data HttpContextBase is able to provide.
Example:
public override void OnAuthorization(AuthorizationContext filterContext)
{
...
var actionDescriptor = filterContext.ActionDescriptor;
var currentAction = actionDescriptor.ActionName;
var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
var isAuthorized = securitySettingsProvider.IsAuthorized(
currenPrincipal, currentAction, currentController, hasHttpPostAttribute, hasHttpGetAttribute);
...
}
This is why I can't implement my authorization logic inside the AuthorizeCore() method override since it gets only HttpContextBase as the parameter and what I need to make an authorization decision is AuthorizationContext.
This leads me to put my authorization logic to the OnAuthorization() method override as in the example above.
But here we come to the second limitation:
The AuthorizeCore() method is called by the caching system to make an authorization decision whether the current request should be served with the cached ActionResult or corresponding controller method should be used to create a new ActionResult.
So we can't just forget about the AuthorizeCore() and use the OnAuthorization() only.
And here we're returning to the initial point:
How to make authorization decision for the cache system based on the HttpContextBase only if we need more data from the AuthorizationContext?
with many subsequent questions like:
How are we supposed to properly implement the AuthorizeCore() in
this case?
Should I implement my own caching to let it supply
sufficient data to the authorization system? And how it can be done
if yes?
Or I should say good-bye to the caching for all controller
methods protected with my custom System.Web.Mvc.AuthorizeAttribute?
It must be said here that I'm going to use my custom System.Web.Mvc.AuthorizeAttribute as a global filter and this is
the complete good-bye to the caching if the answer to this
question is yes.
So the main question here:
What is the possible approaches around to deal with such custom authorization and proper caching?
UPDATE 1 (Additional information to address some possible answers around):
There is no gurantee in the MVC that every single instance of the
AuthorizeAttribute would serve single request. It can be reused
for many requests (see
here for more info):
Action filter attributes must be immutable, since they may be cached
by parts of the pipeline and reused. Depending on where this attribute
is declared in your application, this opens a timing attack, which a
malicious site visitor could then exploit to grant himself access to
any action he wishes.
In the other words, AuthorizeAttribute MUST be immutable and
MUST NOT share state between any method calls.
Moreover in the
AuthorizeAttribute-as-global-filter scenario, a single instance of
the AuthorizeAttribute is used to serve all request.
If you think that you save AuthorizationContext in the OnAuthorization() for a request, you're then able to get it in subsequent AuthorizeCore() for the same request, you're wrong.
As a result you would take authorization decision for the current request based on the AuthorizationContext from the other request.
If a AuthorizeCore() is triggered by the caching layer, OnAuthorization() has never called before for the current request (please refer the sources of the AuthorizeAttribute starting from CacheValidateHandler() down to AuthorizeCore()).
In the other words, if request is going to be served using the cached ActionResult, only the AuthorizeCore() would be called and not the OnAuthorization().
So you're unable to save AuthorizationContext anyway in this case.
Therefore, sharing the AuthorizationContext between the OnAuthorization() and AuthorizeCore() is not the option!
the OnAuthorization method is called before the AuthorizeCore method. So you can save the current context for later processing:
public class MyAttribute: AuthorizeAttribute
{
# Warning - this code doesn't work - see comments
private AuthorizationContext _currentContext;
public override void OnAuthorization(AuthorizationContext filterContext)
{
_currentContext = filterContext;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// use _currentContext
}
}
Edit
Since this will not work as Alexander pointed out. The second option could be to completely override the OnAuthorization method:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{
throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
}
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (AuthorizeCore(filterContext.HttpContext))
{
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
var actionDescriptor = filterContext.ActionDescriptor;
var currentAction = actionDescriptor.ActionName;
var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
// fill the data parameter which is null by default
cachePolicy.AddValidationCallback(CacheValidateHandler, new { actionDescriptor : actionDescriptor, currentAction: currentAction, currentController: currentController, hasHttpPostAttribute : hasHttpPostAttribute, hasHttpGetAttribute: hasHttpGetAttribute });
}
else
{
HandleUnauthorizedRequest(filterContext);
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
// the data will contain AuthorizationContext attributes
bool isAuthorized = myAuthorizationLogic(httpContext, data);
return (isAuthorized) ? HttpValidationStatus.Valid : httpValidationStatus.IgnoreThisRequest;
}
In my filterConfig, I have registered a global attribute filter, which requires authorization on each of my methods.
However, I have one particular method where I want to apply a different authorization filter attribute. How does one accomplish this, if at all possible?
Note: I do not want to use the [AllowAnonymous] attribute (which works seamlessly and completely ignores my filter), since I want the request to be authorized, just through a different set of authorization logic on the method.
You can alter your filter to allow multiple by setting AllowMultiple = true in the AttributeUsage attribute on your attribute class, and add a check so that if the filter is present multiple times, the globally-applied one doesn't execute. The ActionExecutingContext that gets passed into OnActionExecuting() lets you get the filters applied via filterContext.ActionDescriptor.GetCustomAttributes(), so you can use that here.
Then, alter the constructor so that you can pass in a parameter (probably an enum) that it can use to decide which authorisation method to use - the normal one, or this other one. Give that parameter a default value that makes it select the normal auth method. Then, on that one method that needs a different auth method, you can apply the filter with the other value of the parameter. So it might look like this:
public class CustomAuthAttribute : AuthorizeAttribute
{
public CustomAuthAttribute(AuthMethod method = AuthMethod.StandardAuth)
{
//stuff
}
}
[CustomAuth(AuthMethod.WeirdAuth)]
public ActionResult MethodThatNeedsDifferentAuth()
{
//stuff
}
you can write your own version of the authorize attribute and pass specific parameter to depending on what action would you like your attribute to do for example
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string currentAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (currentAction != "notallowed")
{
HandleUnauthorizedRequest(filterContext);
}
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
context.Result = new RedirectResult("/home/login");
}
and then apply it to your class or action
[CustomAuthorize(currentAction = "notallowed")]
public class HomeController : Controller