Sie sind auf Seite 1von 32

CUSTOM ROLES BASED ACCESS CONTROL (RBAC) IN

ASP.NET MVC APPLICATIONS

PART 1

ARTICLE

FRAMEWORK INTRODUCTION

PROJECT:
AUTHOR:
REPORT DATE:

CUSTOM ROLES BASED ACCESS CONTROL (RBAC)


STEFAN WLOCH
13TH FEBRUARY 2015

Contents
Introduction ...................................................................................................................................................................... 3
Background ....................................................................................................................................................................... 3
Using default ASP.NET Roles and Membership API .......................................................................................................... 3
Custom Controller/Action Authorization .......................................................................................................................... 4
Authorization ................................................................................................................................................................ 4
Roles Based Access Control (RBAC) .............................................................................................................................. 4
Preparing the Database .................................................................................................................................................... 5
Retrieving User Application Permissions from our RBAC Tables .................................................................................. 5
User Roles/Permissions Class Mapping ............................................................................................................................ 6
A Basic Overview of MVC Controller Action Methods ...................................................................................................... 7
Roles and MVC Controller/Action Associations ................................................................................................................ 8
Action Filters ................................................................................................................................................................... 10
Custom Action Filters .................................................................................................................................................. 10
Restricting Access to MVC Controller Action Methods using Action Filters ............................................................... 12
Performing Conditional Processing using RBAC during Controller Action Execution ................................................. 13
Using RBAC in a Controllers Action Method .............................................................................................................. 15
Using RBAC in a Controllers View .............................................................................................................................. 16
Using RBAC to Dynamically Control Menus ................................................................................................................ 17
Persisting RBAC Data via ADO.NET Entity Framework (EF)............................................................................................. 18
Creating an Entity Framework (EF) RBAC Model ............................................................................................................ 19
Generated Entity Framework (EF) RBAC Model ......................................................................................................... 20
Extracting Data from our Entity Framework (EF) RBAC Model ................................................................................... 21
Reading User Roles/Permissions from our Database ................................................................................................. 22
Code Listing for Entity Framework (EF) RBAC Data Retrieval ................................................................................. 22
Extending the RBAC Framework ..................................................................................................................................... 23
Sample Project ................................................................................................................................................................ 24
Application Permissions .............................................................................................................................................. 24
Application Roles ........................................................................................................................................................ 25
Application Users ........................................................................................................................................................ 26
Adding RBAC to Existing MVC Applications ................................................................................................................ 27
jQuery User Interface .................................................................................................................................................. 29
Removing jQuery User Interface References .............................................................................................................. 30
RBAC Authentication/Authorisation Overview ............................................................................................................... 31
Conclusion ....................................................................................................................................................................... 32

Page 2

Introduction
In this post, I shall cover implementing custom Roles Based Access Control (RBAC) and subsequent roles
maintenance in the context of an intranet based ASP.NET MVC web application using Windows Authentication.
ASP.NET Roles and Membership provides almost all features required to perform authentication and authorization but
adding a new role and assigning it to a particular user seems to have been lost. This solution forms a self-contained
framework independent of default out of the box providers. The framework allows us to focus on which features/areas
in our application are restricted to the user, including menus, and what information to make visible/invisible to the user
without concerning ourselves with the underlying technicalities. The framework offers RBAC functionality inside the
controller action and controller view at a granular level whilst using minimum code syntax and the framework can be
extended to incorporate custom RBAC methods. It is especially suited for corporate intranet applications where there
is restricted access to the hosting web server once your web application has been deployed or the administration of
user roles including role assignment cannot be directly undertaken by the applications system administrator or owner.

Background
Developing intranet applications within an organization based on Windows Authentication has been around since the
dawn of the intranet. In most organizations, its typical that intranet based applications not only require to permit
access to a subset of users defined in the organizations Active Directory server, but to also define roles which are
assigned to the applications users thus restricting access to certain features/areas within the application. Again,
Roles Based Access Control isnt a new concept and there are numerous examples posted that exemplify this concept
in one form or another. ASP.NET MVC aligns itself well for RBAC and the examples posted on the web in their
various guises either over engineer the concept or are too simplistic averting extendibility. Its for this reason that I
wrote this article.

Using default ASP.NET Roles and Membership API


The default ASP.NET Roles and Membership classes come in very handy when we want to provide authentication
and authorization in our applications. This approach requires you to define roles upfront which are then referenced in
the MVC application via function attributes; essentially hardcoding values.
[Authorize(Roles="Administrators")]
public class AdminController : Controller
{
. . .
}

Several problems with this approach become immediately obvious...

Without recompiling and re-deploying my application, how do I create new custom roles and bind them
dynamically to controller methods once my application has been deployed?
How do I dynamically associate users with multiple roles where the highest application permission takes
access precedence?
How do I dynamically control menus or controller view rendering based on the requesting users role(s) and
associated application permissions?

When developing corporate solutions, we generally find ourselves in situations where the applications users role data
needs to be stored in the applications own database. If a database server fails due to hardware failure, restoring an
earlier backed-up copy of the applications database will contain all the role data thus aligning well for database
replication for the purpose of a hot standby database server.

Page 3

Custom Controller/Action Authorization


Choosing to implement our own custom authentication/authorization mechanism will entail abandoning the default out
of the box ASP.NET Roles and Membership authentication/authorization mechanism. However, in doing so enables
for finer granularity over user roles and application rights. Generally speaking, an application once deployed should
be self-maintaining and regulating via the applications system administrator; it becomes a time consuming and costly
affair when an application developer inherently becomes the users/roles administrator for that application unless the
intention is to capitalize on the reliance of support teams maintaining backend roles.

Authorization
Role based applications are where users in the system are assigned specific roles. In our system, each role
determines which areas of the application the role can access via application permissions. Application permissions
define MVC controller names and controller action names represented as a string concatenation of the two properties
in the format controller-action (eg admin-index). Application permissions are unique which can be traced back to
their controller-action references. It's easy to get confused with the difference between user authentication and user
authorization. In summary, authentication is verifying that users are who they say they are, using some form of login
mechanism (username/password, Windows Authentication, and so on something that says this is who I am).
Authorization is verifying that they can perform tasks as part of their job role with respect to your site. This is usually
achieved using some type of role-based system.

Roles Based Access Control (RBAC)


Roles Based Access Control is an approach to restricting system access to authorised users. This mechanism can be
used to protect users from accessing parts of the system that they do not need. It also can be used to restrict access
to data which they do not need to see.
Roles are created for various job functions and its not uncommon for new roles to be introduced into a role-based
system long after the application has been deployed. The permissions to perform certain operations are assigned to
specific roles. Users are assigned particular roles, and through those role assignments acquire application
permissions to perform particular computer-system functions. Since users are not assigned permissions directly, but
only acquire them through their role (or roles), management of individual user permissions becomes a matter of simply
assigning appropriate roles to the user's account. Each user in the system can be assigned zero, one or many roles
depending on their responsibility within the business processes.

Page 4

Preparing the Database


In order to form the basis of our authentication/authorization framework, we will need to add several tables to the
applications existing database, if one currently exists, or create a new application database that will contain the
tables. Theses tables are derived from the following Entity-Relationship (ER) diagram.
RBAC Entity-Relationship Diagram

USER

Is Assigned

ROLE

Is Assigned

PERMISSION

Our Entity-Relationship diagram implies that an application user can be assigned zero or many application roles. An
application role can be assigned zero or many application permissions. Application permission represents controller
action methods.
Subsequently, we derive the following database tables from our Entity-Relationship diagram.

USERS
User_Id
Username

LNK_USER_ROLE
User_Id
Role_Id

ROLES

LNK_ROLE_PERMISSION

Role_Id
RoleName
IsSysAdmin

Role_Id
Permission_Id

PERMISSIONS
Permission_Id
PermissionName

We would clearly use more table properties in our real-world application allowing for flexible customization but the
illustrated tables provide the minimum properties required to form the basis of an RBAC framework. Integrating our
custom authentication/authorization mechanism into existing MVC applications should be relatively straight forward
since no additional databases or Identity Management providers (IdMs) are needed. It would be highly unlikely that
any MVC application wishing to introduce RBAC would be operating without a backend database in the first place
therefore we could simply add the above tables to the existing database. However, we do have the option to separate
our RBAC tables away from the main application database since our RBAC tables are independent and based on a
loose coupling design. Any MVC application operating without a backend database will generally not need RBAC,
examples being unit/currency conversion websites.

Retrieving User Application Permissions from our RBAC Tables


The following SQL will retrieve a users application permissions from our database tables and is used here for
illustrative purposes.
SELECT Permission_Id, PermissionName FROM PERMISSIONS
WHERE Permission_Id IN (
SELECT DISTINCT(Permission_Id)
FROM LNK_ROLE_PERMISSION
WHERE Role_Id IN (SELECT DISTINCT(Role_Id)
FROM LNK_USER_ROLE ur
JOIN USERS u ON u.User_Id=ur.User_Id
WHERE u.Username='swloch'))

Page 5

User Roles/Permissions Class Mapping


Let us now represent a users associated roles and application permissions using a single class encapsulating the
necessary worker methods to check the users role(s) and associated permissions. We will take a closer look at the
GetDatabaseUserRolesPermissions() method at a later stage. This class will be used throughout our application
to determine user authorization.
Note: Code snippets presented in this article are minimal intended for illustration purposes only in order to focus on
the topic at hand. The sample project available for download expands on the illustrated code.
public class RBACUser
{
public int User_Id { get; set; }
public bool IsSysAdmin { get; set; }
public string Username { get; set; }
private List<UserRole> Roles = new List<UserRole>();
public RBACUser(string _username)
{
this.Username = _username;
this.IsSysAdmin = false;
GetDatabaseUserRolesPermissions();
}
private void GetDatabaseUserRolesPermissions()
{

//Get user roles and permissions from database tables...


}
public bool HasPermission(string requiredPermission)
{
bool bFound = false;
foreach (UserRole role in this.Roles)
{
bFound = (role.Permissions.Where(p => p.PermissionDescription == requiredPermission).ToList().Count > 0);
if (bFound)
break;
}
return bFound;
}
public bool HasRole(string role)
{
return (Roles.Where(p => p.RoleName == role).ToList().Count > 0);
}
}
public class UserRole
{
public int Role_Id { get; set; }
public string RoleName { get; set; }
public List<RolePermission> Permissions = new List<RolePermission>();
}
public class RolePermission
{
public int Permission_Id { get; set; }
public string PermissionDescription { get; set; }
}

The RBACUser class encapsulates custom user authentication/authorization functionality and will be executed in an
action filter which supports pre-action behaviour to controller action methods. Action Filters are explained in the
following section.

Page 6

A Basic Overview of MVC Controller Action Methods


MVC controllers are responsible for responding to requests made against an ASP.NET MVC website. MVC attempts
to map each browser request to a particular controller action. If no controller action is specified in the URL, as in the
example given below, the default controller action index is used. If the underlying controller or controller action does
not exist, a HTTP 404 error will be returned to the browser. For example, entering the following URL into a web
browser will cause MVC to attempt to invoke the Index action in the Admin controller.

http://localhost/Admin
The Admin verb in the URL signifies the controller name due to its position in the URL path. In this example, the
AdminController class is invoked; MVCs controller class naming convention is to append the keyword Controller
to the controllers name. An MVC controller class is responsible for processing and responding to browser requests.
Every controller class should expose controller action methods that get invoked via URL references or other paths.
The following URL specifies both a controller name and controller action method.

http://localhost/Admin/Create
MVC will attempt to invoke the Create controller action method in the AdminController class. Every controller
action returns an action result in response to a browser request even if the referenced controller or controller action
doesnt exist. Before executing an invoked controller action method, pre-processing can be instructed by using an
Action Filter where logic can be placed to determine if the action method should be executed or directed to another
part of the system instead. An Action Filter is an ideal candidate for checking a users authorization for the invoked
functionality.

Page 7

Roles and MVC Controller/Action Associations


Now that we have a basic understanding on how controller action methods are invoked and that MVC provides a way
for us to process logic before an action method is executed in order to evaluate whether the invoked action method
should be executed, we need to understand the association between controller action methods and our application
roles. In our system, each role will have a number of application controller-action associations defined; recall that
application permissions define MVC controller names and controller action names formatted as controller-action.
Lets consider the following example; the Administrator role defined in our application must have the ability to create
new users via the Admin controller using the Create action method. Therefore, we require to associate the
application permission admin-create with the Administrator role. Likewise, the Standard User role defined in our
application should not have this ability and therefore must not have the application permission association. Our
example create user action method simply returns a page to the browser containing text fields and a submit button
that enables the administrator to create a new user in the backend database. Clearly, we dont want this page
accessible to anybody.
We need to create the application permission admin-create and assign the permission to our Administrator role
since the controllers name and action method name combination will be passed to our authorization logic as the
required application permission to be evaluated against the requesting users allowed application permissions. You
can change the format of the application permission stored in the database to a format you prefer but it needs to be
consistent throughout the application.
For the time being, we shall manually create and assign the application permission in our database to gain an
understanding on how our RBAC tables are structured but the sample project available for download provides an
administration menu that enables a system administrator to perform CRUD actions on users/roles/permissions
dynamically.
--Create an 'Administrator' Role setting IsSystemRole=1
INSERT INTO ROLES(RoleName, IsSysAdmin) VALUES('Administrator', 1)
--Create a 'Standard User' Role setting IsSystemRole=0
INSERT INTO ROLES(RoleName, IsSysAdmin) VALUES('Standard User', 0)
--Create an Application Permission for the action method 'Create'
--defined in the 'Admin' controller (ie 'admin-create')
INSERT INTO PERMISSIONS(PermissionDescription) VALUES('admin-create')
--Associate the Application Permission 'admin-create' with the
--'Administrator' Role
INSERT INTO LNK_ROLE_PERMISSION VALUES(
(SELECT Role_Id FROM ROLES WHERE RoleName = 'Administrator'),
(SELECT Permission_Id FROM PERMISSIONS WHERE PermissionDescription = 'admin-create'))

The following tables display the content view of the ROLES,


PERMISSIONS and LNK_ROLE_PERMISSION tables respectively in
relation to the above SQL INSERT commands.

Page 8

Now that we have defined an application role and assigned one application permission to the new role, we need to
create an application user who is assigned the Administrator role. To keep things simple, we shall assume that our
application is running as an intranet based system using Integrated Windows authentication via IIS where the user
requesting the resource has already be authenticated. We simply use the IPrincipal object to identify the
requesting users name to which we map their unique username to our USERS table.
It goes without saying that only users permitted to access the intranet site would be added to the USERS table unless
you are adopting a user registration mechanism where users would be assigned standard user role. This also
works perfectly well for web sites not based on Integrated Windows authentication where authentication is achieved
and tracked using authentication tokens. In either case, the user is identified via the IPrincipal object.
Lets assume that the user to which we shall assign the application administration role has the Windows username
swloch and that the IPrincipal.Identity.Name property for this user evaluates to somedomain\swloch.
We need to add the user to the USERS table using Windows username (without the prepended domain name) as the
table Username field and assign the Administrator role to the user.
--Create the user 'swloch'
INSERT INTO USERS(Username) VALUES('swloch')
--Associate the 'Administrator' Role with user
INSERT INTO LNK_USER_ROLE VALUES(
(SELECT User_Id FROM USERS WHERE Username = 'swloch'),
(SELECT Role_Id FROM ROLES WHERE RoleName = 'Administrator'))

The following tables display the content view of the USERS and LNK_USER_ROLE
tables respectively in relation to the above SQL INSERT commands.
.

Before a controllers action is executed, the requesting users role is determined to check whether the role contains
the requested controller/action combination. If the role does contain the controller/action association then execution of
the controller action is permitted resulting in the
action result page from that controller/action being
returned. If the role does not contain the required
http://localhost/admin/create
controller/action association, an invalid authorisation
page is returned instead of the action result page.
User Request
(Invoke
authorization filter)

Authorisation Check
Does users role(s) contains
required controller-action
association?

RBAC
RBAC Database
Database

YES
NO

Action Result

Unauthorized Page
Response

Admin Page

Unauthorized Message Page

Page 9

Action Filters
In MVC, controllers define action methods that usually have a one-to-one relationship with possible user interactions,
such as clicking a link or submitting a form. Occasionally, you want to perform logic either before an action method is
called or after an action method runs. To support this, MVC provides action filters. Action filters are custom attributes
that provide a declarative means to add pre-action and post-action behaviour to controller action methods.
ASP.NET MVC provides the following types of action filters:

Authorization filter, which makes security decisions about whether to execute an action method, such as
performing authentication or validating properties of the request. The AuthorizeAttribute class is one
example of an authorization filter.

Action filter, which wraps the action method execution. This filter can perform additional processing, such as
providing extra data to the action method, inspecting the return value, or cancelling execution of the action
method.

Result filter, which wraps execution of the ActionResult object. This filter can perform additional processing
of the result, such as modifying the HTTP response. The OutputCacheAttribute class is one example of a
result filter.

Exception filter, which executes if there is an unhandled exception thrown somewhere in action method,
starting with the authorization filters and ending with the execution of the result. Exception filters can be used
for tasks such as logging or displaying an error page. The HandleErrorAttribute class is one example of
an exception filter.

Custom Action Filters


Lets create a custom authorization filter named RBACAttribute inherited from the AuthorizeAttribute class. Our
custom attribute extracts the requested controller name and controller action name properties from the
AuthorizationContext object and checks whether the derived application permission exists as a permitted
permission in any of the requesting users assigned roles using the RBACUser object.
public class RBACAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Create permission string based on the requested controller name and action name in the format 'controllername-action'
string requiredPermission = String.Format("{0}-{1}",
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName);
//Create an instance of our custom user authorization object passing requesting user's 'Windows Username' into constructor
RBACUser requestingUser = new RBACUser(filterContext.RequestContext.HttpContext.User.Identity.Name);
//Check if the requesting user has the permission to run the controller's action
if (!requestingUser.HasPermission(requiredPermission) & !requestingUser.IsSysAdmin)
{
//User doesn't have the required permission, return our custom 401 Unauthorized access error
//Since we are setting filterContext.Result to contain an ActionResult page, the controller's action will not be run.
//The custom 401 Unauthorized access error will be returned to the browser in response to the initial request.
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "action", "Index" },
{ "controller", "Unauthorised" } });
}
//If the user has the permission to run the controller's action, then filterContext.Result will be uninitialized and
//executing the controller's action is dependant on whether filterContext.Result is uninitialized.
}
}

The RBACUser object exposes the HasPermission method that accepts a permission parameter returning a bool
value denoting the existence of that permission in any of the users assigned roles. If you derive a class from the
AuthorizeAttribute class, the derived class must be thread safe. Therefore, do not store state information in an
instance field in an instance of the class unless that state information is meant to apply to all requests. Instead, store
state information per request in the Items property, which is accessible through the context objects passed to
AuthorizeAttribute.
Page 10

In the event where the users role(s) do not contain the required application permission, a customized 401
Unauthorized access error is returned instead of the intended controllers action result view. The customized error
simply returns a view, as detailed below, via the Index controller action defined in the Unauthorized controller
invoked by the RedirectToRouteResult.

The error text is defined in the Index.cshtml file corresponding to the Unauthorized controller and you should modify
this file to change the pages aesthetics.
UnauthorizedController.cs
public class UnauthorisedController : Controller
{
// GET: Unauthorised
public ActionResult Index()
{
Session.Abandon();
return View();
}
}

Index.cshtml corresponding to the Unauthorized controller


@{
ViewBag.Title = "Unauthorised Request";
}
<h1>Error 401 : Unauthorised Request</h1>
<p />
<h2>
You do not have permission to access the requested resource due to security restrictions.
In order to gain access, please speak to your system administrator.
</h2>

Page 11

Restricting Access to MVC Controller Action Methods using Action Filters


We can now use our custom authorization filter to restrict access to a controllers action method in our web application
by decorating the action method with our RBACAttribute attribute; MVC allows you to omit the Attribute verb from
the authorization attribute when decorating a controllers action to simply use RBAC for those who prefer this naming
convention. Our authorization attribute instructs MVC to perform logic before the action method is called where logic
checks whether the requesting user has been authenticated and has the required application permission to execute
the controllers action method.
[RBAC]
public ActionResult Create()
{
return View();
}
OR
[RBACAttribute]
public ActionResult Create()
{
return View();
}

A good approach to security is to always place the security check as close as possible to the resource you are
securing. You may have additional checks higher up the stack, but ultimately, you need to secure the actual resource.
This way, no matter how the user gets to the resource, there will always be a security check in place. In this case, you
dont want to rely on routing and URL authorization to secure a controller; you really need to secure the controller
itself.
Our custom RBACAttribute authorization attribute serves this purpose.

If you dont specify any roles or users, the current user must simply be authenticated in order to call the action
method. This is an easy way to block unauthenticated users from a particular controller action.

If a user attempts to access an action method with this attribute applied and fails the authorization check, the
filter causes the server to return a 401 Unauthorized HTTP status code.

Page 12

Performing Conditional Processing using RBAC during Controller Action Execution


Lets consider that we have a controller action method that returns a page containing employee data. This time, we
are not decorating the controllers action with our custom RBACAttribute attribute since all users registered with our
application may access this page. However, we do wish to restrict the data returned inside our controllers action
during execution based on the requesting users role(s).
For example, a user having the 'Standard' user role will have access to the controllers action (no authorization filter
applied to the controllers action) but only be allowed to view the Employee's work related contact details whereas a
user having the 'HumanResourcesManager' role will be allowed to view additional information including the
employee's salary. Therefore, we require to check the requesting users role both in the controllers action method
and corresponding view to determine the level of employee data returned back to the browser.
Since our RBACUser class encapsulates the required functionality to evaluate a users roles/permissions, we need to
expose this functionality to the controllers action methods and views. The simplest way is to expose our custom
methods to the System.Web.Mvc.ControllerBase abstract class using extension methods. This enables our users
roles/permissions functionality to be available to every controller during execution keeping code changes to an
absolute minimum, even if you have numerous controllers and corresponding views since there will be no need to
change every controller with a new controller class inherited from Controller which exposes our functionality.
Let us extend the System.Web.Mvc.ControllerBase class to expose our RBACUser functionality necessary for
roles/permissions management.
public static class RBAC_ExtendedMethods
{
public static bool HasRole(this ControllerBase controller, string role)
{
bool Found = false;
try
{
//Check if the requesting user has the specified role...
Found = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).HasRole(role);
}
catch { }
return Found;
}
public static bool HasPermission(this ControllerBase controller, string permission)
{
bool Found = false;
try
{
//Check if the requesting user has the specified application permission...
Found = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).HasPermission(permission);
}
catch { }
return Found;
}
public static bool IsSysAdmin(this ControllerBase controller)
{
bool IsSysAdmin = false;
try
{
//Check if the requesting user has the System Administrator privilege...
IsSysAdmin = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).IsSysAdmin;
}
catch { }
return IsSysAdmin;
}
}

Page 13

We can now call our exposed functionality in any controller action and/or corresponding view through the controllers
context object as illustrated below.
Controller Action (EmployeeController.cs)

RBACUser functionality exposed via our RBAC_ExtendedMethods class can be used in controller actions.

Controller Action View (Index.cshtml)

RBACUser functionality exposed via our RBAC_ExtendedMethods class can be used in views.

Page 14

Using RBAC in a Controllers Action Method


The following listing illustrates the use of our custom HasRole and HasPermission methods, exposed in our
RBACUser class, in the controllers action through the controllers context object. We have extended these methods to
the controllers context object using extension methods defined in our RBAC_ExtendedMethods class.
public class EmployeeController : Controller
{
// GET: Employee
public ActionResult Index()
{
if (this.HasRole("HumanResourcesManager"))
{
//This code block is permitted as the requesting user has the 'HumanResourcesManager' role assigned.
//Perform additional tasks and/or extract additional data from the database into the controller's
//view model/viewbag in order to be passed down to the controller's view.
}
if (this.HasPermission("ViewRestrictedHRData"))
{
//This code block is permitted as the requesting user has the 'ViewRestrictedHRData' permission assigned.
//We can also define role functionality permissions not related to controller-action access.
//Extract salary data from database into controller's view model/viewbag...
}
return View();
}
}

Without having to alter our EmployeeController class, we now have our RBACUser functionality exposed through the
controllers context object using extension methods. This is also true for the controllers view.

Page 15

Using RBAC in a Controllers View


The following listing illustrates the use of our custom HasRole and HasPermission methods. We have extended
these methods to the controllers context object using extension methods defined in our RBAC_ExtendedMethods
class.
@{
ViewBag.Title = "Employee Page";
}

@{
if (ViewContext.Controller.HasRole("HumanResourcesManager"))
{
<p>
Use this area to provide additional information and/or display
additional data provided in the model/viewbag by the controller's action
as the user has the "HumanResourcesManager" role assigned.
</p>
}
if (ViewContext.Controller.HasPermission("ViewRestrictedHRData"))
{
<p>
Use this area to provide additional information and/or display
additional data provided in the model/viewbag by the controller's action
as the user has the "ViewRestrictedHRData" permission assigned.
</p>
}
}
<p>Use this area to provide standard information.</p>

Our RBAC_ExtendedMethods class provides us with a flexible framework which enables us to extend our custom
RBAC functionality with minimal effort. Our extended RBAC methods are automatically exposed to every controller
action and corresponding view in our application through the controllers context object without having to change the
controller classes in anyway (except for the use of our newly exposed functionality).

Page 16

Using RBAC to Dynamically Control Menus


Our custom HasPermission, HasRole and IsSysAdmin methods come in useful when displaying dynamic menu
items. Recall that each role in our system will have a number of application controller-action associations defined
each representing a controllers name and controllers action name. Consider the application menu items displayed
below.

If we need to dynamically display menu items based on the requesting users role permissions, we can simply refer to
the menus target controller name and controller action as the permission in the format controller-action.
For example, if we require the Import Data menu to be visible only to users allowed access to the underlying
controllers action, we would simply wrap our custom HasPermission method around the menu item definition
(generally found in the _Layout.cshtml view) as illustrated below passing the menus target controller name and
controller action as the permission to be checked.
<body>
<div class="page">
...
<div id="menucontainer">
<ul>
@{
if (ViewContext.Controller.IsSysAdmin())
{
<li>
<a href="#" class="arrow">System Administration</a>
...
</li>
}
}
@{
if (ViewContext.Controller.HasPermission("data-import"))
{
<li>@Html.ActionLink("Import Data", "Import", "Data")</li>
}
}
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Home", "Index", "Home")</li>
</ul>
</div>
</div>
...
</body>

The custom HasPermission method will check the requesting users role(s) for the permission data-import and
display accordingly. Additionally, we can also make use of the IsSysAdmin method to check whether the requesting
user has a role that has the IsSysAdmin property enabled. Therefore, a user that doesnt have a System
Administrator role nor a role defining the data-import permission will see the following menu items displayed (based
on the illustrated code snippet) as opposed to the menu items displayed above.

However, if a user enters a URL directly (eg http:///Data/Import) and does not have the required permission, a
customized 401 Unauthorized access error is returned instead of the intended controllers action result view
providing we have decorated the controllers action or controller class with our RBACAttribute. Associating the
data-import permission to the users role will automatically display the corresponding menu.

Page 17

Persisting RBAC Data via ADO.NET Entity Framework (EF)


Because every request to a controller action decorated with our RBACAttribute authorization attribute requires
verification of the requesting users permitted application permissions stored in our RBAC database, the users
roles/permissions data should be read from the RBAC database once and then stored persistently for maximum
performance. Busy websites can be exposed to thousands of requests per minute and reading the requesting users
roles/permissions data from the RBAC database for every request would seriously hinder the websites performance.
ADO.NET Entity Framework (EF) is a component of the .NET Framework which provides a data persistence layer
using a conceptual model, called the Entity Data Model (EDM), which sits on top of the database schema. EF
applications can run on any computer on which the .NET Framework (starting with version 3.5 SP1) is installed. EF
persists data in-memory using a data model that is mapped to underlying database tables; data changes to the
underlying tables are automatically detected and automatically refreshed in the persistent data model. This makes EF
an ideal candidate for storing our RBAC data (even if your existing application isnt using EF) since performance will
be dramatically increased as the RBAC data isnt read from the underlying database every time we query the data
model. Once our roles/permissions are correctly configured, updates will be infrequent.

Page 18

Creating an Entity Framework (EF) RBAC Model


Lets create a new EF model to store our RBAC data which will be used by our RBACUser object. Add a new
ADO.NET Entity Data Model item to your existing project as detailed below (unless you already have a model in
which case you simply need to add the table associations into your models OnModelCreating method).

Name your database connection string accordingly. The name


specified will be stored in the applications Web.Config file as a
database connection string located in the <connectionStrings>
configuration section as detailed below.
<connectionStrings>
<add name="RBAC_Model" connectionString="data source=localhost;initial..." providerName="System.Data.SqlClient" />
</connectionStrings>

Page 19

Generated Entity Framework (EF) RBAC Model


The following database context model and corresponding database entities are generated from our RBAC database
tables; we access the applications RBAC data through the context model.
namespace RBAC.Models
{
public partial class RBAC_Model : DbContext
{
public RBAC_Model(): base("name=RBAC_Model")
{
}
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Permission>()
.HasMany(e => e.Roles)
.WithMany(e => e.Permissions)
.Map(m => m.ToTable("LNK_ROLE_PERMISSION").MapLeftKey("Permission_Id").MapRightKey("Role_Id"));
modelBuilder.Entity<Role>()
.HasMany(e => e.Users)
.WithMany(e => e.Roles)
.Map(m => m.ToTable("LNK_USER_ROLE").MapLeftKey("Role_Id").MapRightKey("User_Id"));
}
}
}
[Table("USERS")]
public partial class User
{
public User()
{
Roles = new HashSet<Role>();
}
[Key]
public int User_Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
[Table("ROLES")]
public partial class Role
{
public Role()
{
Permissions = new HashSet<Permission>();
Users = new HashSet<User>();
}
[Key]
public
public
public
public

int Role_Id { get; set; }


string RoleName { get; set; }
string RoleDescription { get; set; }
bool IsSysAdmin { get; set; }

public virtual ICollection<Permission> Permissions { get; set; }


public virtual ICollection<User> Users { get; set; }
}
[Table("PERMISSIONS")]
public partial class Permission
{
public Permission()
{
Roles = new HashSet<Role>();
}
[Key]
public int Permission_Id { get; set; }
public string PermissionDescription { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
Page 20

The generated model has identified and defined three entities (User, Role and Permission) from our RBAC database
and defined the RBAC_Model context model which links the entity relationships together. The OnModelCreating
method on the RBAC_Model context model class is called during the database context model creation where model
entity relationships are created which correspond to the underlying database tables including table index keys and
referential integrity constraints.
NOTE: If you already have an Entity Data Model (EDM) in your existing application, simply add the generated entity
classes (Users, Roles and Permissions) to your project and define the corresponding DbSet for these entities in your
model including the entity relationships defined in the OnModelCreating method. So long as the RBAC tables are
added to your existing database, there will be no need to add a database connection string to the applications
Web.Config since your application will already have a connection string defined for your existing EDM.

Extracting Data from our Entity Framework (EF) RBAC Model


To create an instance of our RBAC EF context model, we simply create an instance of the RBAC_Model class from
which we then search against the database entities (ie Users, Roles and Permissions). The following listing illustrates
the creation of our context model and a search against the Users table for swloch.

//Create an instance of the RBAC Entity Data Model (EDM)...


using (RBAC_Model _data = new RBAC_Model())
{
User _user = _data.Users.Where(u => u.Username == swloch).FirstOrDefault();
if (_user != null)
{
//User found, access roles/permissions via exposed properties...
foreach (Role _role in _user.Roles)
{
foreach (Permission _permission in _role.Permissions)
{
if (_permission.PermissionDescription == 'admin-create')
{
}
}
}
}
}

The first time a DbContext is created, is pretty expensive but once the object has been created much of the
information is cached so that subsequent instantiations are significantly quicker. You are more likely to see
performance problems from keeping a context object around than you are from instantiating one each time you need
access to your database. If you keep a context object around it will keep track of all the updates, additions, deletes
etc and this will slow your application down and may even cause subtle bugs to appear in your application.
The context object should be created per request. Create the context object, do what you need to do with the object
and then get rid of it. Do not try and have a global context (this is not how web applications work).

Page 21

Reading User Roles/Permissions from our Database


We will now take a closer look at the GetDatabaseUserRolesPermissions() method used in our RBACUser class.

Code Listing for Entity Framework (EF) RBAC Data Retrieval


The following listing details the GetDatabaseUserRolesPermissions() method which extracts the requesting users
RBAC data from the underlying database using ADO.NET Entity Framework. When a new instance of the RBAC
context model is created for the very first time, data will be retrieved from the database and stored in-memory.
Therefore, subsequent new instances of the RBAC context model will not require data to be loaded directly from the
underlying database since EF will use the data already stored in-memory. Any underlying changes to the data in the
database are detected by EF and reloaded when we next access the data thus ensuring data retrieval from our
persistence data layer is always up-to-date.
private void GetDatabaseUserRolesPermissions()
{
using (RBAC_Model _data = new RBAC_Model())
{
User _user = _data.Users.Where(u => u.UserName == this.Username).FirstOrDefault();
if (_user != null)
{
this.User_Id = _user.Id;
foreach (Role _role in _user.Roles)
{
UserRole _userRole = new UserRole { Role_Id = _role.Id, RoleName = _role.RoleName };
foreach (Permission _permission in _role.Permissions)
{
_userRole.Permissions.Add(new RolePermission { Permission_Id = _permission.Id,
PermissionDescription = _permission.PermissionDescription });
}
this.Roles.Add(_userRole);
if (!this.IsSystemAdmin)
this.IsSystemAdmin = _role.IsSysAdmin;
}
}
}
}

Page 22

Extending the RBAC Framework


If we wish to extend our RBAC framework with customized methods, we simply add new methods to the RBACUser
class; these methods could be associated with new fields added to the USERS database table or associated tables.
For example, we could add a new column to the USERS database table called Title which represents the users
name title (eg Mr, Prof, Dr etc). By adding the new property Title to our RBACUser class as highlighted below, the
property will be automatically loaded from the database into our RBACUser object by Entity Framework.
We could then expose a new method in our RBACUser class called IsDoctor() which checks for the value Dr
returning a bool (true if value equals Dr otherwise false). Although this is an unlikely real world example, it
demonstrates the concept.
public class RBACUser
{
public int User_Id { get; set; }
public bool IsSysAdmin { get; set; }
public string Username { get; set; }
public string Title { get; set; }
public bool IsDoctor()
{
return (this.Title == "Dr");
}
...
...
}

In order to expose our new functionality to the applications controller actions and controller views, we must wrap our
new functionality in new methods in our RBAC_ExtendedMethods class (ie our extension methods).
public static class RBAC_ExtendedMethods
{
public static bool IsDoctor(this ControllerBase controller)
{
bool IsDoctor = false;
try
{
//Check if the requesting user has the specified role...
IsDoctor = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).IsDoctor();
}
catch { }
return IsDoctor;
}
...
...
}

We have now extended our RBAC framework with our customized method IsDoctor(). We can now use the new
method in our controller action via this.IsDoctor() syntax or in our controller view via
ViewContext.Controller.IsDoctor() syntax.
Note: Code snippets used in the above examples are minimal highlighting additional code only in order to focus on
the topic at hand.

Page 23

Sample Project
The sample project available for download implements the AdminController which provides the necessary RBAC
administration. Simply adding this controller, accompanying views contained in the Admin folder and RBAC model to
any existing MVC project will provide the necessary RBAC administration functionality. Integrating the RBAC
administration functionality into an existing ASP.NET MVC project is discussed in the next section.
The RBAC administration is exposed via the System Administration menu as displayed below and will only be
visible to a user having a role that has the IsSysAdmin option enabled; the
menu item definition must be contained within the IsSysAdmin function to
display dynamically as discussed in the Using RBAC to Dynamically Control
Menus section. The menu style is driven by CSS and can be easily modified to
follow any application theme.
Before we create any application roles, we need to create permissions
associated with our application. Depending on which areas of your application
you need to restrict using role based access, there may be a large number of
controller action methods which translate to application permissions. Entering
each controller action as a permission into your application can be a dull and time consuming task. To aid in the
creation of your application permissions, the Permissions screen contains a button labeled Import Permissions.

Application Permissions
The import permissions function uses the .NET Frameworks Reflection API which enables the fetching of assembly
type information at runtime. The function iterates through the assemblys MVC controller methods and saves each
controller-action to the permissions database table.

Page 24

Application Roles
Once the applications permissions have been defined, we are in a position to create user roles. User roles are
typically associated with one or more application permissions. Your application business rules should define which
roles in your application should access which areas. Clicking on the Roles menu will display your applications roles
(where defined) and enable CRUD actions on the roles to be undertaken.

Once an application role has been created, application permissions can then be assigned to the role. Permissions
can be associated and disassociated with a role at any time. Associating permissions with roles can be a time
consuming task. To aid in the role-permission association process, the Add All Permissions button associates all
permissions with the role in a single action; unwanted permissions can then be disassociated using the trash icon.
Alternatively, individual permissions can be selected from the dropdown and added via the Add Permission button.

Page 25

NOTE: Permanently deleting an application permission via the Permissions screen will automatically remove the
permission from associated roles.

Application Users
Once the applications roles have been defined, Users can be created and assigned roles.

Roles can be associated and disassociated with a user at any time. Individual roles are assigned to a user by
selecting the role from the dropdown and pressing the Add Role button; unwanted roles can be unassigned using
the trash icon.

NOTE: Permanently deleting an application role via the Roles screen will automatically remove the role from
associated users.
Page 26

Adding RBAC to Existing MVC Applications


Adding the RBAC functionality to an existing application will require the following steps to be undertaken:1. Using Solution Explorer in Visual Studio, create a new
folder in your project called Action Filters and add the
RBACAttribute.cs file by right-clicking on the newly
created folder and selecting Add >> Existing Item.
This file defines the RBACAttribute used to decorate
controller action methods.
Add the file RBAC_ExtendedMethods.cs to your Action
Filters folder in the same manner as above. This file
defines the class RBAC_ExtendedMethods which
defines the extension methods HasRole,
HasPermission and IsSysAdmin
2. Using Solution Explorer in Visual Studio, create a new
folder in your project inside the Content folder called
Images and add the grid icons (bin.png and pen.png)
unless you prefer to use images already present in your
application in which case you will need to alter the
image references to point to your preferred images.
3. Using Solution Explorer in Visual Studio, create a new
folder in your project inside the Content folder called
themes and add the file jquery-ui-1.10.3.min.css
including the subdirectories ui-lightness and images
as detailed opposite; ensure you include the
corresponding files from each folder. The applied
theme, named ui-lightness, was downloaded from
http://jqueryui.com but you can easily replace or remove
this theme.
4. Using Solution Explorer in Visual Studio, add both the
AdminController.cs and
UnauthorisedController.cs files to your
Controllers folder by right-clicking and selecting Add
>> Existing Item. These files define the RBAC
System Administration controller functionality.
5. Using Solution Explorer in Visual Studio, add the files
PERMISSIONS.cs, ROLE.cs, USER.cs and
RBAC_Model.cs to your Models folder. These files
define the RBAC_Model data model and corresponding
data model types (User, Role, Permission).
6. Using Solution Explorer in Visual Studio, create a new
folder in your applications Views folder called Admin
and add the view files as detailed in the RBACDemo
project by right-clicking on the newly created folder and
selecting Add >> Existing Item. These views are
associated with the Admin controller actions. If your
project already contains an Admin controller, you can
rename the RBAC Admin controller and move the
associated views to the new corresponding folder.
Links which reference the Admin controller will need
Page 27

changing to reference the newly named controller. These links are contained in both the controller itself and
the corresponding views.
7. Using Solution Explorer in Visual Studio, create a new folder in your applications Views folder called
Unauthorised and add the view file Index.cs by right-clicking on the newly created folder and selecting
Add >> Existing Item.
8. Include the System Administration menu in your _Layout.cshtml file using your existing menu CSS
styling. If you prefer the illustrated menu styling, copy the necessary CSS from the sample Site.css file to
your projects Site.css file. This also applies to the views; incorporate your own styling.
9. The Scripts folder references the jquery-ui-1.10.3.custom.min.js file but you can freely download your own
custom themed version from http://jqueryui.com and reference accordingly.
http://jqueryui.com

If you do download your own custom version of jQuery UI, using Solution Explorer in Visual Studio, add the file
jquery-ui-1.10.x.custom.min.js to the Scripts folder otherwise the jQuery dialog will not render correctly.

Page 28

jQuery User Interface


The jQuery User Interface can be freely downloaded from http://jqueryui.com where custom themes can be
generated. The easiest way is to use the ThemeRoller to build a theme. This application will generate a jquery-ui1.10.x.custom.min.css file, an images directory containing all the necessary background images and icon sprites
and a jquery-ui-1.10.x.custom.min.js file which can simply be dropped into your project.
The jquery-ui-1.10.x.custom.min.css and jquery-ui-1.10.x.custom.min.js files require to be referenced in your
_Layout.cshtml file as follows.
NOTE: If you download a custom theme based on jQuery UI Version 1.11.x, file names will differ (ie jquery-uitheme.min.css and jquery-ui.min.js)
_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RBAC Demo</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
@Scripts.Render("~/bundles/modernizr")
<!-- JQuery Library References -->
<script src="@Url.Content("~/Scripts/jquery-1.10.2.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.10.3.custom.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-common.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-validate.unobtrusive.js")" type="text/javascript"></script>
<!-- jQuery UI Theme -->
<link href="@Url.Content("~/Content/themes/ui-lightness/jquery-ui-1.10.3.min.css")" type="text/css" ... />
</head>
<body>
...

The applied theme, named ui-lightness, was downloaded from http://jqueryui.com.


The following screenshot illustrates a correctly configured dialog box.

Page 29

Removing the applied theme will prevent dialog boxes (used for deleting grid items) from rendering correctly.
Although the dialog box will display and still operate, it will be displayed incorrectly. The following screenshot
illustrates an incorrectly displayed dialog box due to a theme being incorrectly configured or missing.

However, you may wish to remove the jQuery User Interface completely so that the user is not prompted before
deleting the item or you can provide an alternative interface for the dialog box.

Removing jQuery User Interface References


If you prefer not to use the jQuery User Interface, simply remove the jquery-ui-xxx.css file reference from your
_Layout.cshtml file and delete the themes folder located inside the Content folder. Where a jQuery UI dialog box
was previously invoked, modify the code as illustrated in the following example using the correct controller action
method and corresponding parameters.
<table id="RoleTable" style="width: 90%">
...
@foreach (var item in Model)
{
<tr>
<td>
@Html.ActionLink(item.RoleDescription, "RoleDetails", new { id = item.Role_Id })
</td>
...
<td>
<center>
<a href="@Url.Action("RoleEdit", "Admin", new { id = item.Role_Id })">
<img src="@Url.Content("~/Content/Images/pen.png")" alt="Edit Role" />
</a>
<a href="@Url.Action("RoleDelete", "Admin", new { id = item.Role_Id })">
<img src="@Url.Content("~/Content/Images/bin.png")" alt="Delete Role" />
</a>
</center>
</td>
</tr>
}
</table>
...

The trash icon displayed in the grid will now call the controller action method directly passing the required parameters.
However, no dialog warning will be displayed to the user thus preventing the user from cancelling the delete operation.
Alternatively, you could invoke a JavaScript function, utilizing the JavaScript alert keyword, via the onclick event
which will display a basic dialog warning not reliant on the jQuery User Interface. It really comes down to how
sophisticated you wish to make your User Interface.
There are pros and cons to each of these methods, and depending on your product, market, and niche, one may be
more suitable for you than the others.

Page 30

RBAC Authentication/Authorisation Overview


The IIS User Authentication process is undertaken by IIS to provide an additional layer of security. Since the web
application is intranet based and will run within a Windows domain, IIS will check that a user making an inbound
request to the web server has been authenticated. If a user is not authenticated, IIS reroutes the inbound request to
an Authentication Login dialog box requesting alternative user credentials. If a user is authenticated, the inbound
request is routed to the MVC web application where authorisation checks are undertaken as described in earlier
sections.
The following diagram illustrates the RBAC Authentication/Authorisation process.

IIS

ASP.NET MVC

RBAC DATABASE

HTTP GET REQUEST

ENTITY
FRAMEWORK

http://localhost/admin/create

PERMISSIONS
Permission_Id

Is User
Authenticated?

User Request
(Invoke
authorization filter)

User IS
Authenticated

User NOT Authenticated

LNK_ROLE_PERMISSION

Authorisation Check
Does users role(s) contains
required controller-action
association?

Display User
Authentication
dialog box

Role_Id
Permission_Id

User Data

YES
Action Result

RBACUser
Object

NO

Database
Database
Create User Page

Unauthorized Page
Response

ROLES
Role_Id

User Data

Admin
Create User
Filename

Unauthorized Message Page

Transfer Date

HTTP RESPONSE

Create User
Page Response

LNK_USER_ROLE

Username

Unauthorized Request

User_Id
Role_Id

Error 401: Unauthorized Request


Save

You do not have permission to access the


requested resource due to security restrictions.
In order to gain access, please speak with your
system administrator.
USERS

Unauthorized
Page Response

User_Id
IsSysAdmin
Username

An inbound request to our web application is initially handled by IIS which authenticates the user against the active
directory group via an Authentication Login dialog box if not authenticated. If the user is authenticated, the request is
forwarded to the MVC web application which checks the users rights and roles. A users right will decide whether the
requested controller/action can be processed. A user is stored as an object in the Entity Framework layer which
populates the users data from the database tables.

Page 31

Conclusion
This solution forms an ideal framework for any intranet application that requires dynamic self-contained Roles Based
Access Control (RBAC) that is specific to the application and independent of ASP.NET Roles and Membership and
Identity Management providers (IdM) such as Microsoft Identity Integration Server (MIIS). The framework can be
added to existing projects as well as new developments and once deployed will be self-maintaining and regulating via
the applications system administrator with little or no reliance on the application developer.
This solution is particularly suited for corporate intranet applications where limited access to the deployed web server
is granted or the administration of user roles including role assignment is delegated away from the application system
administrator or owner.
In the next post, the framework will be extended to incorporate role based reporting.

Page 32

Das könnte Ihnen auch gefallen