I am unable to make my custom authorization attribute return anything but a 401 unauthorized on controller methods.
I've setup a very basic Custom Authorization Attribute:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
filters.Add(new CustomAuthorizationAttribute()); //<---Custom
}
The class itself:
public class CustomAuthorizationAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return true;
}
protected override HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
{
return base.OnCacheAuthorization(httpContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
I've decorated my controller's actions with the CustomAuthorizationAttribute:
[HttpPost]
[CustomAuthorization]
public ActionResult UploadFile(FormCollection fileUpload)
{...
Obviously I must be missing something simple because this code always results in a 401 unauthorized response.
The AuthorizeCore method breakpoint does not get hit, but occasionally OnAuthorization method does.
I'm new to MVC5. What am I missing? How do I make this authorization work based on my custom criteria?
Thanks in advance.
There appears to be a triple redundancy with your authorize attributes. You've added three authorize filters to a single controller action. Two here:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
filters.Add(new CustomAuthorizationAttribute()); //<---Custom
}
... and one here:
[HttpPost]
[CustomAuthorization]
public ActionResult UploadFile(FormCollection fileUpload)
I'm guessing that the first AuthorizeAttribute (vanilla) is applied first, hits its own AuthorizeCore(), returns false and sends your context onto 401-ville.
Try removing both global filter registrations, then see if the controller action behaves as you expect.
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
}
Related
I'm writing unit tests using Microsoft.VisualStudio.TestTools.UnitTesting and Moq for an ASP.NET MVC 5 app that has a custom authorization filter added via FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizeRedirect());
//...
}
}
Here's the custom authorization attribute:
public class AuthorizeRedirect: AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
action = "Redirect",
controller = "Error"
}));
}
}
}
I'm using a slightly modified version of the IsAnonymous method from here: https://datatellblog.wordpress.com/2015/05/05/unit-testing-asp-net-mvc-authorization/. However it only checks the method/controller for explicitly defined [AuthorizeRedirect] attribute; in its absence, it considers the action to be anonymous.
Is it possible to detect that the above attribute has been applied globally in a unit test when checking if an action is allowing anonymous access?
Here's one way to test for it: see if it's added to the global filters,
// Arrange
var coll = new GlobalFilterCollection();
// Act
FilterConfig.RegisterGlobalFilters(coll);
var authorized = coll.Any(x => x.Instance is AuthorizeRedirect);
// Assert
Assert.IsTrue(authorized);
I have a LoggingAttribute which logs request and response in OnActionExecuted method:
public class LoggingAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext httpContext)
{
//Logger.Log();
}
}
There is another attribute for validating the request and return BadRequest. This return response from OnActionExecuting method:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
Now, when I apply both these attributes on Action method, my BadRequests are not being logged but when I apply LoggingAttribute on controller level and ValidateModelAttribute on action method, BadRequests are getting logged (OnActionExecuted of LoggingAttribute getting called).
Can someone please explain this behaviour i.e. OnActionExecuted getting called even when action method is not being executed when attribute applied at controller.
You need to apply LoggingAttribute first on action method,
[LoggingAttribute]
[ValidateModelAttribute]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
I tried you scenario like as below at my end
[HttpGet]
[ValidateModelAttribute]
[LoggingAttribute]
public void test()
{
//throw new NotImplementedException("This method is not implemented");
}
With the same code as yours and I found same issue as you , your LogginAttribute not get called because you are setting repose for context in your ValidateModelAttribute, as request get response it returns immediately(because of this actionContext.Response = ) as request got response , and then it even doesnt call you method on which you applied attribute.
So solution for this part is you have to write OnActionExecuting which get called before your Validationattribute OnActionExecuting method, and your code will log as OnActionExecuting method of LoggingAttribute before you are returning response.
public class LoggingAttribute : ActionFilterAttribute
{
public override void OnActionExecuting
(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//Logger.Log();
}
public override void OnActionExecuted(HttpActionExecutedContext httpContext)
{
//Logger.Log();
}
}
and also change order or attribute , reason for doing below is when Response is set then in that case it get return from that point only , so whatever filter is there in pipe line after than is not get called.
[HttpGet]
[LoggingAttribute]
[ValidateModelAttribute]
public void test()
{
//throw new NotImplementedException("This method is not implemented");
}
as suggested in below answer by #programtreasures
I would like to implement Anti-CSRF token in Global.asax file of MVC 3.
Is that possible to implement the same in Gloabl.asax file.
Seems what you need is to create a custom filter class which implements IAuthorizationFilter for all POST methods, by checking HttpContext.Request.HttpMethod request:
public class ValidateAntiForgeryTokenEveryPost : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
if (context.HttpContext.Request.HttpMethod == "POST")
{
System.Web.Helpers.AntiForgery.Validate();
}
}
}
Then, add the new filter in FilterConfig class:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ValidateAntiForgeryTokenEveryPost());
}
}
Also ensure that the custom filter has registered in Global.asax code:
protected void Application_Start()
{
// other settings
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// other settings
}
By using global filtering given above, all POST method requests are automatically checks for AntiForgeryToken, no matter if #Html.AntiForgeryToken() is not present inside view pages.
Addendum 1:
It is possible to exclude certain actions from CSRF token checking, what you need is preventing Validate method to execute while a custom attribute class is present. First, create a custom attribute class for validation check:
[AttributeUsage(AttributeTargets.Method)]
public class ExcludeAntiForgeryCheckAttribute : Attribute
{
// other stuff
}
Afterwards, use ActionDescriptor.GetCustomAttributes to get custom attribute type created above:
public class ValidateAntiForgeryTokenEveryPost : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
// adapted from Darin Dimitrov (/a/34588606/)
bool isValidate = !context.ActionDescriptor.GetCustomAttributes(typeof(ExcludeAntiForgeryCheckAttribute), true).Any();
// use AND operator (&&) if you want to exclude POST requests marked with custom attribute
// otherwise, use OR operator (||)
if (context.HttpContext.Request.HttpMethod == "POST" && isValidate)
{
System.Web.Helpers.AntiForgery.Validate();
}
}
}
Then you can decorate any methods which should be exempted from CSRF validation token:
[HttpPost]
[ExcludeAntiForgeryCheck]
public ActionResult Index(ViewModel model)
{
// other stuff
return View(model);
}
References:
Check CRSF token by default in ASP.NET MVC (standard version)
Securing all forms using AntiForgeryToken (attribute-based version)
I don't think so. For each request we need to check token.
Please try to use below code in view file.
#Html.AntiForgeryToken()
I have 2 controllers Home with
public class HomeController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some irrelevant stuff
base.OnActionExecuting(filterContext);
}
public ActionResult Index()
{
return View();
}
}
and Service with
public ActionResult Confirm()
{ return RedirectToAction("Index", "Home");}
And one ActionFilterAttribute with OnActionExecuting method
public class InvitationModeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some stuff
base.OnActionExecuting(filterContext);
}
}
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new InvitationModeAttribute());
}
}
When I go to localhost/Service/Confirm , OnActionExecuting is fired, but then when RedirectToAction is called, OnActionExecuting is not fired.
How can I catch this after RedirectToAction?
Thanks
Refer this For More clarity
First of all
Remove OnActionExecuting method in controller level
public class HomeController : Controller
{
[InvitationModeAttribute]
public ActionResult Index()
{
return View();
}
}
2nd Controller
public class ServiceController : Controller
{
[InvitationModeAttribute]
public ActionResult Confirm()
{
return RedirectToAction("Index", "Home");
}
}
From MSDN
Scope of Action Filters
In addition to marking individual action methods with an action
filter, you can mark a controller class as a whole with an action
filter. In that case, the filter applies to all action methods of that
controller. Additionally, if your controller derives from another
controller, the base controller might have its own action-filter
attributes. Likewise, if your controller overrides an action method
from a base controller, the method might have its own action-filter
attributes and those it inherits from the overridden action method. To
make it easier to understand how action filters work together, action
methods are grouped into scopes. A scope defines where the attribute
applies, such as whether it marks a class or a method, and whether it
marks a base class or a derived class.
Is it possible to make an action filter or something that runs before the action method itself runs on the controller?
I need this to analyze a few values in the request before the action runs.
You can override OnActionExecuting method in controller class
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
//Your logic is here...
}
You could use an attribute:
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Your logic here...
base.OnActionExecuting(filterContext);
}
}
And if you want to apply it to all controllers, in your Global.asax.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyFilterAttribute());
}
protected void Application_Start()
{
// Other code removed for clarity of this example...
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Other code removed for clarity of this example...
}
If u don't want to use a base controller u can also add a own HttpHandler and register it in the web.config. In the BeginProcessRequest method u can analyse the values.