0

I have the following database structure DB structure, authentication/authorization part and I have to build authentication/authorization for an ASP.NET MVC 5 site.

The DB schema works as follows: every user belongs to a group, and each group can be granted/denied a permission. Each permission matches an action on a controller (let's say, a Petitions controller would have the following actions: List, View, Add, Edit, Delete, VoteFor, VoteAgainst, Reject and Approve, and each action is an entry in the Permissions table).

The purpose of all this is that, each time a user invokes an action, the site verifies that the user belongs to a group that has been granted permission over that action, and reacts accordingly.

An example: let's say the admin grants the PetitionsList, PetitionsView, PetitionsApprove and PetitionsReject permissions to the Managers group, and the PetitionsList, PetitionsView, PetitionsAdd, PetitionsVoteFor and PetitionsVoteAgainst to the Users group.

In this case, both groups can

  • list petitions
  • view a petition

Managers can

  • approve a petition
  • reject a petition

but they can't

  • vote for a petition
  • vote against a petition.

In the same way, users can

  • add a petition
  • vote for a petition
  • vote against a petition

but they can't:

  • approve a petition
  • reject a petition

and neither can edit or delete a petition.

I'd really like to leverage the attributes functionality in MVC 5. My idea is to build custom attributes that do all the authentication/authorization behind scenes. Something like this:

public class PetitionsController : Controller
{
    [MyCustomAuth(Permission="PetitionsList",Groups="Users,Managers")]
    public ActionResult List()
    {
        //show the list of petitions
    }

    [MyCustomAuth(Permission="PetitionsView",Groups="Users,Managers")]
    public ActionResult View()
    {
        //show a specific petition
    }

    [MyCustomAuth(Permission="PetitionsAdd",Groups="Users")]
    public ActionResult Add()
    {
        //show add petition form
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsAdd",Groups="Users")]
    public ActionResult Add(object[] params)
    {
        //save new petition
    }

    [MyCustomAuth(Permission="PetitionsEdit",Groups="Admins")]
    public ActionResult Edit(int id)
    {
        //show edit petition form
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsEdit",Groups="Admins")]
    public ActionResult Edit(object[] params)
    {
        //save changes to petition
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsDelete",Groups="Admins")]
    public ActionResult Delete(int_id)
    {
        //delete petition
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsVoteFor",Groups="Users")]
    public ActionResult VoteFor(int id)
    {
        //add vote supporting petition
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsVoteAgainst",Groups="Users")]
    public ActionResult VoteAgainst(int id)
    {
        //add vote against petition
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsApprove",Groups="Managers")]
    public ActionResult Approve(int id)
    {
        //approve petition
    }

    [HttpPost]
    [MyCustomAuth(Permission="PetitionsReject",Groups="Managers")]
    public ActionResult Reject(int id)
    {
        //reject petition
    }
}

Please note the MyCustomAuth attribute over every action. I want that attribute to do the heavy lifting of saying if the user is actually authorized to do that action, behind the scenes. Of course, in case it's not authenticated/authorized, the attribute should redirect to login page/401/somewhere else.

My question is, where do I start? Am I expected to implement some special interface/inherit from some class in MVC 5? Or do I have to write this from scratch?

Also, thanks in advance for reading this wall of text and for giving me any tips/pointers in the right direction.

adiga
  • 34,372
  • 9
  • 61
  • 83
Léster
  • 1,177
  • 1
  • 17
  • 39
  • Generally, database access code should not be in attributes (can't unit test the code), so a better solution would be to have a service that return a value indicating if the user has the correct permission of not. The code in each controller method might be something like `if (!_permissionService.HasPermission(DocumentType.Petitions, ActionType.Edit, user.ID) { // denied }` –  Oct 13 '16 at 05:01
  • How about creating a service that verifies the user has access to the specified action and calling the service from the attribute class? Makes testing the verification easier and I still get to write only an attribute over the action (the best of both worlds?). – Léster Oct 14 '16 at 19:26
  • Just ensure that you can use DI so that you controllers can be united tested. Suggest you look at the 2 top answers [here](http://stackoverflow.com/questions/7192543/injecting-dependencies-into-asp-net-mvc-3-action-filters-whats-wrong-with-this) –  Oct 15 '16 at 01:17

1 Answers1

1

I think you can start with below Custom Authorization Filter. It also handle cases for both - ajax request and full page request.

 public class MyCustomAuthAttribute : FilterAttribute, IAuthorizationFilter
    {
        public string Permission { get; set; }
        public string Groups { get; set; }

        public void OnAuthorization(AuthorizationContext filterContext)
        {
            bool isauthorized = CheckIfUserIsAuthorized();
            if (!isauthorized)
                context.Result = new HttpUnauthorizedResult(); // mark unauthorized

            // Only do something if we are about to give a HttpUnauthorizedResult and we are in AJAX mode.
            if (filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.Result = new JsonResult
                {
                    Data = new { Success = false, Message = "Unauthorized Access" },
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };
            }
            else
            {
                base.OnAuthorization(filterContext);
                if (filterContext.Result is HttpUnauthorizedResult)
                {
                    HttpContext.Current.Session.Abandon();
                    System.Web.Security.FormsAuthentication.SignOut();
                    filterContext.Result = new RedirectResult("Your Login Page.");
                }
            }
        }

        private bool IsAuthorizedUser()
        {
            // use Permission, Groups and your logic
        }
    }

and use it the same way you mentioned:

 [MyCustomAuth(Permission="PetitionsEdit",Groups="Admins")]
Ankush Jain
  • 5,654
  • 4
  • 32
  • 57