When I create a new asp.net mvc 4.0 application, one of the first thing I do, is create and set a custom authorize global filter
like so:
//FilterConfig.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new CustomAuthorizationAttribute());
}
Then I create the CustomAuthorizationAttribute
like so:
//CustomAuthorizationAttribute.cs
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
//Handle AJAX requests
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
{
//Handle regular requests
base.HandleUnauthorizedRequest(filterContext); //let FormsAuthentication make the redirect based on the loginUrl defined in the web.config (if any)
}
}
I have two controllers: HomeController
and SecureController
The HomeController is decorated with the [AllowAnonymous]
attribute.
The SecureController is NOT decorated with the [AllowAnonymous]
attribute.
The Index() ActionResult
of the HomeController
displays a View with a simple button.
When I click the button, I make an ajax call to a GetData() method that lives inside the SecureController
like so:
$("#btnButton").click(function () {
$.ajax({
url: '@Url.Action("GetData", "Secure")',
type: 'get',
data: {param: "test"},
success: function (data, textStatus, xhr) {
console.log("SUCCESS GET");
}
});
});
Needless to say, when I click the button, I trigger the CustomAuthorizationAttribute
because it is a global filter but also because the SecureController
is NOT decorated with the [AllowAnonymous]
attribute.
Ok, I’m done with my introduction...
With the introduction of asp.net mvc 5.0
, we are now introduced to a new authentication filter
which happens to get triggered before the authorization filter (which is great and gives us more granular control on how I can differentiate a user that is NOT authenticated (http 401) from a user that IS authenticated and who happens to NOT be authorized (http 403)).
In order to give this new authentication filter
a try, I’ve created a new asp.net mvc 5.0 (VS Express 2013 for Web) and started by doing the following:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
filters.Add(new CustomAuthenticationAttribute()); //Notice I'm using the word Authentication and not Authorization
}
Then the attribute:
public class CustomAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
var user = filterContext.HttpContext.User;
if (user == null || !user.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
I’ve created a HomeController
. The HomeController
is decorated with the [AllowAnonymous]
attribute.
Before launching the application from VS 2013, I’ve set two break points inside both methods of my CustomAuthenticationAttribute (OnAuthentication
and OnAuthenticationChallenge
).
When I launch the application, I hit the first break point(OnAuthentication
). Then, to my surprise, the code within the Index() ActionResult
of my HomeController
gets executed and only after I return the View() do I hit the break point on the OnAuthenticationChallenge()
method.
Questions: I have two questions.
Question 1)
I was under the impression that the [AllowAnonymous]
attribute would automagically bypass any code within my CustomAuthenticationAttribute
but I was wrong! Do I need to manually check for the existence of the [AllowAnonymous]
attribute and skip any code?
Question 2)
Why is the code inside my Index()
method of my HomeController
gets executed after the OnAuthentication
? Only to realize that after I return View() do the code inside the OnAuthenticationChallenge()
gets executed?
My concern is that I do not want the code from the Index()
method to get executed if the user is NOT authenticated.
Perhaps I’m looking at this the wrong way.
If anyone can help me shed some light on this, that’d be great!
Sincerely Vince
The AllowAnonymous attribute in MVC is used to skip the authorization which is enforced by Authorization Filter in MVC. Now, run the application and navigate to /Home/NonSecured and you will see that it displays the page as expected and when you navigate to /Home/Secured, then it will redirect you to the Login page.
[AllowAnonymous] bypasses all authorization statements. If you combine [AllowAnonymous] and any [Authorize] attribute, the [Authorize] attributes are ignored. For example if you apply [AllowAnonymous] at the controller level, any [Authorize] attributes on the same controller (or on any action within it) is ignored.
AllowAnonymous lets users who have not been authenticated access the action or controller. In short, it knows based on the token it receives from the client.
You probably know that the ASP.NET MVC Controller itself is also an ActionFilter. The new IAuthenticationFilter provides a great ability to customize authentication within an ASP.NET MVC 5 application.
In answer to Question 1:
The [AllowAnnoymous] attribute acts like a flag (it actually has no implementation logic within it). Its presence is merely checked for by the [Authorize] attribute during execution of OnAuthorization. Decompiling the [Authorize] attribute reveals the logic:
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
[AllowAnnonymous] would never 'automagically' bypass the code in your custom attribute...
So the answer to the second half of Question 1 is: Yes - if you want your custom attribute to react to the presence of the [AllowAnnonymous], then you would need to implement a check (similar to the above) for the [AllowAnnonymous] attribute in your custom [Authorize] attribute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With