How to manage permissions on actions in ASP.NET MVC (Part 3)

Hi everyone,

In this series of articles I made a rant about recommended software templates and techniques (A rant about Microsoft’s ASP.NET MVC templates (part 1)). In the last article (Best ASP.MVC Model usage (Part 2)) I proposed how to make proper use of a model class to apply business and validation logic including actions, states and conditional requirements (on top of typical attributes used for Entity Framework Code First).

In this article I’m gonna address another requirement:

How to make sure that user actions are allowed only under certain conditions ??

So we have this shopping application I’m building where we have a number of views to manage orders:

  1. Index, with a list of all orders, where there is Create button, and a number of buttons for each order like: Edit, Delete, Assign, Send, Cancel
  2. Create, which allows creation of a new order
  3. Edit, to edit an order
  4. Delete, to delete an order
  5. Assign, to assign an open order to an employee
  6. Send, to enter a date when an order was sent to a customer
  7. Cancel, to cancel an order

There can be much more actions in real life systems, so we have to take into consideration the fact that the list of user actions and states is something that changes in time as business requirements change, needs to be strict but open for modification. Also we have to make sure that these actions are allowed only for specific roles in the system and only in certain states of an order. For example you don’t want to allow everybody to delete or cancel an order. Also you don’t want to allow cancelation of an order that has already been sent. The list of such requirements may be very long. So we have to do it systematically.

Web application is a system where you have a client (browser) and a server. It means that you have to manage permissions on both ends. It’s wise to block/disable some actions buttons that you don’t want to be used in some situations. Also it’s required that on the server side you block same actions so nobody breaks rules by mistake made in the view or by hacker who wants to exploit your system by using Postman, Wireshark or Fiddler. It’s desirable that these checks are done in one place (class) not many different places.

– client browser (razor/html) – prevent clicking
– server side app (.Net)  – prevent hacking or mistakes

There are at least three approaches to address these requirements:

  1. Html/view level by utilizing Razor/C# commands (e.g. @if (User.IsInRole("admin") { Html.Action("Create","Create") }), very common and very, very uggly solution reminding old days of PHP/HTML spaghetti. Don’t use it.
  2. Model (or rather viewmodel) based (eg. @Html.ActionLink("Create","Create", Model.IsCreateAllowed) ), requires custom ActionLink helper function and usage of complex viewmodel which consists of lots of booleans. Recommended by some developers, also very common approach. Much better than approach 1.
  3. Custom action link helper function which directly accesses permission system. Recommended by me. In my opinion it is much clearer and it requires much less coding and is less prone to errors and mistakes

Permissions may be hard coded (as if conditions or in some switch or list) or database table based where you store permission-action relations. What you choose is up to you. Database storage means that most likely you want your users to be able to modify permissions using some views in application, so it’s additional work. For purpose of this article I’m going to hard code the permissions.

Let’s concentrate on custom action link helper. In View:  @Html.MyActionLink("Create New", "Create")
(Notice that I don’t use any unusual parameters here).
And in helper class we implement it:

public static MvcHtmlString MyActionLink(this HtmlHelper Helper, String Text, String ActionName)
        {
            IActionAvailability availability = Helper.ViewContext.Controller as IActionAvailability;
            if (availability.IsActionAvailable(ActionName))
            {
                return Helper.ActionLink(Text, ActionName, null, new { @class="btn btn-primary" });
            }
            else
                return Helper.BlankAction(ActionName);
        }

IActionAvailability interface is simply and interface that allows access to controller’s IsActionAvailable functions and works for actions not bound to any particular object instance. Typically it would be “Create“, but also “Index“. IsActionAvailable has access to user’s role list and is able to check if this user has such function allowed. It’s located in controller, because interface requires it, but the fact is that it uses another central class to do the check.

public bool IsActionAvailable(string ActionName)
{
RolePermission perm = Activator.CreateInstance();
return perm.IsActionAvailable(Helpers.UserHelper.GetRoles(this.HttpContext).ToList(), ActionName);
}

RolePermission class may store role-action permission relations. In my small example application permission function is very simple (blocks access if user has no specific role):

private static bool IsActionForRoleAvailable(Actions Action, String RoleName)
        {
            if (RoleName== EmptyRole)
            {
                return false;
            }

            return true;
        }

Above solution works only for actions that are not related to any instance of an object (order, in our case). In case of action buttons related to an existing order, we have to create another action link helper function:

        
        public static MvcHtmlString MyActionLink(this HtmlHelper Helper, object ItemValue, string LinkText, string ActionName,
           object routeValues)
        {
            var repID = Guid.NewGuid().ToString();
            IActionAvailability availability = Helper.ViewContext.Controller as IActionAvailability;

            if (availability.IsActionAvailable(UserHelper.GetRoles(Helper.ViewContext.HttpContext).ToList(), ItemValue, ActionName))
            {
                var lnk = Helper.ActionLink(repID, ActionName, routeValues, new { @class="btn btn-success" });
                var html = MvcHtmlString.Create(lnk.ToString().Replace(repID, LinkText));
                return html;
            }
            else
                return Helper.BlankAction(ActionName);
        }

This function gets order instance as a first argument (item/ItemValue of IModel interface), and is used in the Index view in the following way: @Html.MyActionLink(item, "Edit", "Edit", new { id=item.OrderId } )
First we use IsActionAvailable version mentioned above which checks roles against actions. Then we use a function which contains information which action is allowed in which state of an order:

        public bool IsActionAvailable(Actions Action)
        {
           
            if (Action == Actions.Edit && this.StatusEnum != Statuses.Open)
                return false;
            [... some simmilar code for other actions here...]
            return true;
        }

One final touch is that we use the very same IsActionAvailable mechanism on server side in OrderController, for example for Edit action:

        public ActionResult Edit(int? id)
        {
            return ProcessGetAction(
                ()=>IsActionAvailable(id, Order.Actions.Edit), 
                Order.Actions.Edit.ToString(), 
                ()=>repo.Get(id),
                () => SetUpCreateEditViewControls(id));
        }

The helper function ProcessGetAction checks if an action is available and then set’s up the Edit View if it is. In my opinion it’s better than using standard Authorize attribute, because we need to check not only authorization but also if action is available in current state of an order, which is not easily available at attribute level. Also, standard usage of Authorize attribute requires string literals as roles, which I hate. The only place I put Authorize attribute is over OrderController class itself, so it’s entirely protected against not logged users.

And that’s it ! We’ve created one system that checks permissions both on client and server side and adopts view and controller accordingly. Here is how the Index view lookes like for a user without any specific roles in the system. Note that only Create and Details action buttons are actually clickable in this example case. This is because the user I used to log in has no role in the system, and I’ve decided that such user has access only to two actions.

In next articles I will show you how to perform these actions, for example how full process of cancelling an order is done from clicking Cancel button, sending POST to the http server, performing bussines method on order (change state to Cancelled) and updating database record. I will include also State Machine that leads between states using actions (triggers). Also, I’m thinking to change ProcessGetAction method into “fluent api”, so it’s more readable and easier to use, and explain in detail why I use it instead of using ASP.NET MVC templates to process controller actions.

Thanks for reading 🙂

Dominik Steinhauf

CEO, .Net developer, software architect at Creative Yellow Solutions (formerly Indesys)

If you need help with your software project, or need customized software for your company, contact me at: dominik.steinhauf ( at) cys.biz.pl

Leave a Reply

Your email address will not be published. Required fields are marked *