ASP.NET MVC Framework allows the developers to build their web application in a more flexible way. Using MVC framework you by passes the headaches of ViewState and Postbacks and also enable your application for testing. In this article we are going to take a look at the Controller Action Role-Based Security.

Introduction:

ASP.NET MVC Framework allows the developers to build their web application in a more flexible way. Using MVC framework you by passes the headaches of ViewState and Postbacks and also enable your application for testing. In this article we are going to take a look at the Controller Action Role-Based Security.

Prerequisite:

If this is your first encounter with ASP.NET MVC Framework then I strongly suggest that you check out the introductory article using the link below:

Getting Started with the ASP.NET MVC Framework

Scenario:

The scenario is really simple. A list of categories is displayed on the page and when the user clicks on the category it will be deleted. But we need to make sure that the user is authorized to delete the items.

Populating the Page with List of Categories:

The first task is to populate the page with a list of categories. Let’s see how this can be implemented.

[ControllerAction]
        public void List()
        {
            NorthwindDataContext northwind = new NorthwindDataContext();
            var list = northwind.Categories;
            RenderView("Categories", list);
        }

The List action is responsible for populating the Categories view with the required data. Let’s check out the Categories view.

public partial class Categories : ViewPage<IEnumerable<AspAllianceMVCApplication.Category>>
    {
       
    }

<% foreach (var category in ViewData)
       { %>
   
    <%= Html.ActionLink<CategoryController>(c => c.Delete(category.id),
        category.CategoryName, new { onclick = "return confirmDelete(" + category.id + ")" })%>
  <br />
 
  <%}  %>

The first thing to note is the Categories class inherits from the ViewPage which is of IEnumerable<Category> type. This means that we will have the strong type support for IEnumerable<Category> in the HTML view of the page. Now, let’s discuss the HTML part of the Categories view.

The foreach loop is used to iterate through the categories. The Html.ActionLink method is used to create hyperlinks which are directed to particular controllers. The first argument to the Html.ActionLink is the Linq expression for the action. The argument c => c.Delete(category.id) means that we are attaching the Delete action to all the categories in the ViewData object. The Delete operation will take a single parameter which is categoryId. The next parameter is the text to appear for the hyperlink. The final parameter is the HTML attributes. We are using onclick attribute of the hyperlink which will popup a confirmation box.

The HTML generated for the page might look something like this:

  <a href="/Category/Delete/1" onclick="return confirmDelete(1)" >Beverages Edite</a>
  <br />
 
  <a href="/Category/Delete/2" onclick="return confirmDelete(2)" >Condiments</a>
  <br />
 
  <a href="/Category/Delete/3" onclick="return confirmDelete(3)" >Confections</a>
  <br />
 
  <a href="/Category/Delete/4" onclick="return confirmDelete(4)" >Dairy Products</a>
  <br />

Now, looking at the URL’s above anyone can easily delete the item by simply copying the URL in the address bar. So, the question is how do we secure the controller actions so only authorized users would be able to delete the items.

Controller Action Security:

ASP.NET MVC Framework is still in its development phases and there is still a lot on the wish list. Maybe in few months the framework will provide us the flexibility to configure action based security easily.

For now let’s use another approach to add security to our controller actions. The OnPreAction event is fired before the action is executed and this seems to be an ideal place to authorize the user. You can override the OnPreAction of the controller class but this solution is not scalable since then you will need to override all the controllers for security purposes. A better approach is to introduce a BaseController and override the OnPreAction of the BaseController. All the controllers will derive from the BaseController class instead of the Controller class. And the BaseController will derive from the Controller class.

protected override bool OnPreAction(string actionName, System.Reflection.MethodInfo methodInfo)
        {
            string controllerName =  methodInfo.DeclaringType.Name;
           
            if(!IsAuthorized(controllerName,actionName)) throw new SecurityException("not authenticated");

            return base.OnPreAction(actionName, methodInfo);
        }

The IsAuthorized custom method is responsible for performing the actual authorization.

private bool IsAuthorized (string controllerName, string actionName)
        {
            System.Web.HttpContext context = System.Web.HttpContext.Current;

            XDocument xDoc = null;

            if (context.Cache["ControllerActionsSecurity"] == null)
            {
                xDoc =  XDocument.Load(context.Server.MapPath("~/ControllerActionsSecurity.xml"));
                context.Cache.Insert("ControllerActionsSecurity",xDoc);
            }

            xDoc = (XDocument) context.Cache["ControllerActionsSecurity"];
            IEnumerable<XElement> elements = xDoc.Element("ControllerSecurity").Elements();

            var role = (from e in elements
                        where ((string)e.Attribute("controllerName")) == controllerName
                        && ((string)e.Attribute("actionName")) == actionName
                        select new { RoleName = e.Attribute("Roles").Value }).SingleOrDefault();

            if (role == null) return true;

            if (!User.IsInRole(role.RoleName))
                    return false;


            return true;
        }

Nothing too complicated! The authorization details are stored in an XML file called ControllerActionsSecurity.xml. Here are the contents of the file:

<ControllerSecurity>
  <add controllerName="CategoryController" actionName="Delete" Roles="Admin" />
</ControllerSecurity>

controllerName: The name of the controller
actionName: The action of the controller
Roles: Authorized roles

If you need to add authorization to a different controller then simply make an entry in the XML file with the appropriate controllerName and the actionName.

Conclusion:

In this article we learned how to authorize the user based on the controller and the action. Hopefully, ASP.NET team will introduce more flexible ways to authorize the users based on their actions.

I hope you liked the article happy coding! 

[Download]