Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5.0 [AllowAnonymous] and the new IAuthenticationFilter

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

like image 616
Vlince Avatar asked Oct 23 '13 19:10

Vlince


People also ask

What is the use of AllowAnonymous in MVC?

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.

What happens if you apply the AllowAnonymous attribute to a controller action that already uses the Authorize attribute?

[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.

What is AllowAnonymous?

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.

Does ASP NET identity use IAuthenticationFilter?

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.


1 Answers

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.

like image 100
JTech Avatar answered Oct 15 '22 11:10

JTech