Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

Role Based Security Architecture Using .NET MVC & JS

4.41/5 (13 votes)
23 Feb 2015CPOL3 min read 30.3K  
A custom security architecture for role based access to components in a page

Introduction

This article presents an architecture for role based access for components/features (e.g., Edit User, Add User, Save Changes) on per page basis. It is a generic solution for role based authorization on both server and client side using .NET MVC and JavaScript.

The application is built as a web application which authenticates and authorizes the user.

How It Works

  • On Login, user details including the features assigned are stored as session object.
  • On request for a page thereon:
    • ServerSide: PermissionFilter authenticates the request; If unauthenticated, user is redirected to Login Page. PermissionFilter also authorizes the requests against the features/permissions assigned; If unauthorized, NotAuthorized page is rendered.
    • ClientSide: PermissionChecker script gets the elementControlIDs user is authorized for and removes the rest elements from the DOM.

Behind the Scenes

  • Database: Each feature has a unique controlID.
  • ClientSide: Each feature is assigned unique controlID using attribute data-feature="<FeatureControlID>".
  • ServerSide: Feature access is checked by the controllerName and actionName associated with a feature from the controller and action name in the request.

Tools & Technologies

  • Visual Studio 2012 - .NET MVC
Code:

The code could be found at:

https://github.com/gtush24/MVCAndJSSecurityApp.git

Background

Database Schema

Image 1

Database Schema
  • Each user has a role.
  • Each role is assigned 1 or more features/actions (Example: Add/Edit/Delete, etc.)
  • Each feature/permission is an action recognized by controllerName and actionName having unique controlID(Name) to recognize it on the webpage.

Examples

  • User - XYZ has a role Admin
  • Role - Admin has assigned features - Edit, Delete
  • Feature - Edit is recognized as ControllerName: Home, ActionName: EditUser

Using the Code

There are 2 main components here for authorization:

Server Side

  • An action filter which authorizes for the actions called against features/permissions assigned to role of loggedIn User.
  • It also validates for authentication; access AfterLogin only pages.
  • It returns a NotAuthorizedToUseThisContent page for any unauthorized activity.

Client Side

  • A script that removes the features from the DOM that are not assigned to the role of loggedIn user from the page on page-load and after completion of any Ajax request.

Server Side - PermissionFilter.cs

It is an action filter and runs on every request to action in a controller. It authenticates and authorizes (using action and controller name from features/permissions allowed for loggedin User role).

C#
public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            HttpRequestBase request = filterContext.HttpContext.Request;
            HttpResponseBase response = filterContext.HttpContext.Response;

            if ((request.AcceptTypes.Contains("application/json")) == false)
            {
                if (request.IsAjaxRequest())
                {
                    #region Preventing caching of ajax request in IE browser
                    response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
                    response.Cache.SetValidUntilExpires(false);
                    response.Cache.SetCacheability(HttpCacheability.NoCache);
                    response.Cache.SetNoStore();
                    #endregion
                }

                string currentActionName = filterContext.ActionDescriptor.ActionName;
                string currentControllerName = 
                     filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;

                //get LoggedInUser Permissions
                PermissionManager userPermissions = PermissionManager.getPermissions();

                //All Features are allowed for SuperAdmin - disablePermissioning
                if (userPermissions != null)
                {
                    //For logout page
                    filterContext.Controller.ViewBag.LoggedInAdministrator = 
                                                 PermissionManager.GetLoggedInUser();

                    if (PermissionManager.enablePermissioningSystem == true)
                    {
                        //Not all actions are feature in the application
                        bool isCurrentActionAFeature = Service.isFeaturePresentInList
                           (userPermissions.allFeatures, currentControllerName, currentActionName);

                        if (isCurrentActionAFeature)
                        {
                            bool hasPermission = false;

                            hasPermission = Service.isFeaturePresentInList
                              (userPermissions.accessibleFeatures, currentControllerName, 
                               currentActionName);

                            if (!hasPermission)
                            {
                                //return 'not authorized' content
                                filterContext.Result = new ViewResult
                                {
                                    ViewName = "~/Views/shared/unauthorizedactivity.cshtml"
                                };
                            }
                        }
                    }
                }
                //Redirect to login Page
                else
                {
                    filterContext.Controller.TempData["Message"] = "Please login to continue.";
                    filterContext.Result = new RedirectToRouteResult(
                                new System.Web.Routing.RouteValueDictionary 
                                { { "controller", "home" }, { "action", "login" }, { "Area", "" } });
                }
            }
        }

On Server, actions are decorated with the PermissionFilter attribute for filtering the requests to that action.

Actions on Server

Client Side - PermissionChecker.js

This script removes the non authorized feature elements from DOM on page load and on any Ajax requests.

JavaScript
var allowedFeatureList = new Array();
var enablePermission = false;
/*Register ajax complete callback to apply 'Userpermissions' on ajax requests*/
/*Ajax OnComplete START*/

//For - Authorizing after ajax response
$(document).ajaxComplete(function (event, request, settings) {

    var allDataFeatureElements = $('[data-feature]');

    if (enablePermission == true) {
        $(allDataFeatureElements).each(function () {
            var currentfid = $(this).data("feature");
            //Check If the current data-feature element in Page lies in allowed Features list
            var result = $.inArray(currentfid, allowedFeatureList);
            if (result != -1) {
            }
            else {
                //$(this).addClass("noDisplay");
                //Remove element from DOM if not authorized to access it
                $(this).remove();
            }
        });
    }
});
/*Ajax OnComplete END*/

//For - Authorizing after page load
$(function () {
    //To disable caching of ajax requests in IE browsers
    $.ajaxSetup({
        cache: false
    });

    var allDataFeatureElements = $('[data-feature]');

    //Get allowed Features List for login user
    $.getJSON("/home/getAllAccessibleFeatureControlIDJSON", function (data) {
        enablePermission = data.enablePerm;
        if (data.enablePerm == true) {
            for (var i = 0; i < data.controlIDS.length; i++) {
                allowedFeatureList.push(data.controlIDS[i]);
            }
            $(allDataFeatureElements).each(function () {
                var currentfid = $(this).data("feature");
                var result = $.inArray(currentfid, allowedFeatureList);
                if (result != -1) {
                }
                else {
                    //$(this).addClass("noDisplay");
                    $(this).remove();
                }
            });
        }
    });
});

Here page-load and ajaxComplete functions are used for DOM filtering.

Features are get from server on page-load only and used in ajaxComplete since getting in ajaxComplete would trigger it again and it would end up being infinite loop.

Feature Request

Feature IDs call from Page

Feature Element

Features Identification

Image 5

UserList Page

In the above image, Logged-In User is assigned only EditUser permission and other features are removed from page by the script.

Image 6

unauthorized activity page

Unauthorized page

Points of Interest

The above requirements could well be dealt with conditional statements in every page and showing elements according to role by filtering them on the server side. But, the above architecture presents a maintained and global solution for such a requirement.

Further Up

The request going each time for features on server could be minimized by storing the values in a cookie and using them thereon in the script.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)