Dynamic Role Based Authorization Asp.net Core | Assign Role from DB

Often times, after you’ve authenticated your user, now you want to authorize what he actually has control over based on his role. A user should only have access for what he’s authorized to control.

In Applications like Admin Panel where your Application is being managed by multiple users, you must manage your users according to their roles.

Typically in an ASP.NET MVC project, this can be achieved by using an authorize attribute, Something like this:

[Authorize(Roles = "Manager, SecondUser")]
public ActionResult HelloWorld()

Now Only “Manager” & “SecondUser” can access the HelloWorld Action.

But what’s the problem here? 

Actually, the above code ties the “Manager” & “SecondUser” with your Controller & Action. So If you want to change User Role in Future, you have to update your code and publish a new version of your Application.

So In this Tutorial, we’ll see the better way to implement Role based Authorization & dynamically Create & Assign Roles to Users.

 If you missed Admin Panel Tutorial, Find here => Creating Admin Panel in Asp.net Core MVC – Step by Step Tutorial


Difference Between Authentication & Authorization

Before Start Implementing Dynamic Role-based Authorization we must know the difference between Authorization & Authentication.

Authentication is actually the process of validating Users Identity by verifying Credentials e.g. Username & Password. Whereas Authorization is the process to validate If a user has rights to access a specific action.

Authorization always comes after the Authentication process.

Now Let’s see how you can Implement Dynamic Role-based Authorization using Asp.net.

Dynamic Role-based Authorization

Database Structure is Important

The database structure is really Important for this. First of all, we should have an Admins Table where we’ll add your users & a Roles Table for adding Roles for Users.

Menus Table is for defining all actions or menus & link_roles_menus is for assigning access to roles.

role based authorization databse

Here’s the Code Database Script. I have also shared the database script inside the GitHub repository.

-- phpMyAdmin SQL Dump
-- version 4.7.7
-- https://www.phpmyadmin.net/
--
-- Host: localhost
-- Generation Time: Nov 28, 2018 at 09:46 PM
-- Server version: 10.1.30-MariaDB
-- PHP Version: 7.2.1

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @[email protected]@CHARACTER_SET_CLIENT */;
/*!40101 SET @[email protected]@CHARACTER_SET_RESULTS */;
/*!40101 SET @[email protected]@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `role_based_authorization`
--

-- --------------------------------------------------------

--
-- Table structure for table `admins`
--

CREATE TABLE `admins` (
  `id` int(11) NOT NULL,
  `full_name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `roles_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `admins`
--

INSERT INTO `admins` (`id`, `full_name`, `email`, `password`, `roles_id`) VALUES
(1, 'Shehryar Khan', '[email protected]', '12345', 1),
(2, 'Ahsan Saeed', '[email protected]', '12345', 2),
(3, 'Shayan tahir', '[email protected]', '12345', 6);

-- --------------------------------------------------------

--
-- Table structure for table `link_roles_menus`
--

CREATE TABLE `link_roles_menus` (
  `id` int(11) NOT NULL,
  `roles_id` int(11) NOT NULL,
  `menus_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `link_roles_menus`
--

INSERT INTO `link_roles_menus` (`id`, `roles_id`, `menus_id`) VALUES
(47, 2, 1),
(48, 2, 2),
(49, 2, 4),
(50, 2, 5),
(51, 2, 6),
(52, 2, 7),
(65, 1, 1),
(66, 1, 2),
(67, 1, 3),
(68, 1, 4),
(69, 1, 5),
(70, 1, 6),
(71, 1, 7),
(76, 6, 1),
(77, 6, 2),
(78, 6, 4);

-- --------------------------------------------------------

--
-- Table structure for table `menus`
--

CREATE TABLE `menus` (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `icon` varchar(50) NOT NULL,
  `url` varchar(255) DEFAULT NULL,
  `parent_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `menus`
--

INSERT INTO `menus` (`id`, `name`, `icon`, `url`, `parent_id`) VALUES
(1, 'Dashboard', 'fa fa-dashboard', '/', 0),
(2, 'Admins', 'fa fa-users', '#', 0),
(3, 'Create Admin', 'fa fa-plus', '/Admins/Create', 2),
(4, 'Manage Admins', 'fa fa-users', '/Admins/Index', 2),
(5, 'Roles', 'fa fa-lock', '#', 0),
(6, 'Create Role', 'fa fa-lock', '/Roles/Create', 5),
(7, 'Manage Roles', 'fa fa-lock', '/Roles/Index', 5);

-- --------------------------------------------------------

--
-- Table structure for table `roles`
--

CREATE TABLE `roles` (
  `id` int(11) NOT NULL,
  `title` varchar(255) NOT NULL,
  `description` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `roles`
--

INSERT INTO `roles` (`id`, `title`, `description`) VALUES
(1, 'Manager', 'Super Admin with all rights...'),
(2, 'Supervisor', 'Can View Dashboard, Admins & Roles'),
(6, 'Developer', 'Can View Dashboard &  Admins List');

--
-- Indexes for dumped tables
--

--
-- Indexes for table `admins`
--
ALTER TABLE `admins`
  ADD PRIMARY KEY (`id`),
  ADD KEY `admins_ibfk_1` (`roles_id`);

--
-- Indexes for table `link_roles_menus`
--
ALTER TABLE `link_roles_menus`
  ADD PRIMARY KEY (`id`),
  ADD KEY `menus_id` (`menus_id`),
  ADD KEY `roles_id` (`roles_id`);

--
-- Indexes for table `menus`
--
ALTER TABLE `menus`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `roles`
--
ALTER TABLE `roles`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `admins`
--
ALTER TABLE `admins`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;

--
-- AUTO_INCREMENT for table `link_roles_menus`
--
ALTER TABLE `link_roles_menus`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=79;

--
-- AUTO_INCREMENT for table `menus`
--
ALTER TABLE `menus`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8;

--
-- AUTO_INCREMENT for table `roles`
--
ALTER TABLE `roles`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;

--
-- Constraints for dumped tables
--

--
-- Constraints for table `admins`
--
ALTER TABLE `admins`
  ADD CONSTRAINT `admins_ibfk_1` FOREIGN KEY (`roles_id`) REFERENCES `roles` (`id`);

--
-- Constraints for table `link_roles_menus`
--
ALTER TABLE `link_roles_menus`
  ADD CONSTRAINT `link_roles_menus_ibfk_1` FOREIGN KEY (`menus_id`) REFERENCES `menus` (`id`),
  ADD CONSTRAINT `link_roles_menus_ibfk_2` FOREIGN KEY (`roles_id`) REFERENCES `roles` (`id`);
COMMIT;

/*!40101 SET [email protected]_CHARACTER_SET_CLIENT */;
/*!40101 SET [email protected]_CHARACTER_SET_RESULTS */;
/*!40101 SET [email protected]_COLLATION_CONNECTION */;

How to Implement

As I have already said, the most important thing here is your Database Tables Linking. Above database is perfect for the easy to implement role-based authorization system.

After Creating a database, simply Add pages for “admins”, “roles”, “menus” Tables with CRUD Operations.

Here’s the Simple CRUD Operations Tutorial with Admin Template => CRUD Operations in Asp.net Core MVC

How to Create Menu from Database

As you can see in “menus” Table that It has a column with the name “parent_id” so It means that menus will be in a Tree format with the Parent-Child relations in the same Table. So I’ll recommend you to Follow This Tutorial to load Data from Database to TreeView for Assigning Menus Access to a Role.

Here’s the Code for Creating Menu from Database.

public class AccountController : Controller
{
    MyDbContext db = new MyDbContext();

    public IActionResult Login()
    {
        return View();
    }

    public ActionResult Validate(Admins admin)
    {
        var _admin = db.Admins.Where(s => s.Email == admin.Email).FirstOrDefault();
        if (_admin != null)
        {
            if (_admin.Password == admin.Password)
            {
                HttpContext.Session.SetString("email", _admin.Email);
                HttpContext.Session.SetInt32("id", _admin.Id);
                HttpContext.Session.SetInt32("role_id", (int)_admin.RolesId);
                HttpContext.Session.SetString("name", _admin.FullName);

                int roleId = (int)HttpContext.Session.GetInt32("role_id");
                List<Menus> menus = db.LinkRolesMenus.Where(s => s.RolesId == roleId).Select(s => s.Menus).ToList();

                DataSet ds = new DataSet();
                ds = ToDataSet(menus);
                DataTable table = ds.Tables[0];
                DataRow[] parentMenus = table.Select("ParentId = 0");

                var sb = new StringBuilder();
                string menuString = GenerateUL(parentMenus, table, sb);
                HttpContext.Session.SetString("menuString", menuString);
                HttpContext.Session.SetString("menus", JsonConvert.SerializeObject(menus));

                return Json(new { status = true, message = "Login Successfull!" });
            }
            else
            {
                return Json(new { status = true, message = "Invalid Password!" });
            }
        }
        else
        {
            return Json(new { status = false, message = "Invalid Email!" });
        }
    }

    private string GenerateUL(DataRow[] menu, DataTable table, StringBuilder sb)
    {
        if (menu.Length > 0)
        {
            foreach (DataRow dr in menu)
            {
                string url = dr["Url"].ToString();
                string menuText = dr["Name"].ToString();
                string icon = dr["Icon"].ToString();

                if (url != "#")
                {
                    string line = String.Format(@"<li><a href=""{0}""><i class=""{2}""></i> <span>{1}</span></a></li>", url, menuText, icon);
                    sb.Append(line);
                }

                string pid = dr["Id"].ToString();
                string parentId = dr["ParentId"].ToString();

                DataRow[] subMenu = table.Select(String.Format("ParentId = '{0}'", pid));
                if (subMenu.Length > 0 && !pid.Equals(parentId))
                {
                    string line = String.Format(@"<li class=""treeview""><a href=""#""><i class=""{0}""></i> <span>{1}</span><span class=""pull-right-container""><i class=""fa fa-angle-left pull-right""></i></span></a><ul class=""treeview-menu"">", icon, menuText);
                    var subMenuBuilder = new StringBuilder();
                    sb.AppendLine(line);
                    sb.Append(GenerateUL(subMenu, table, subMenuBuilder));
                    sb.Append("</ul></li>");
                }
            }
        }
        return sb.ToString();
    }

    public DataSet ToDataSet<T>(List<T> items)
    {
        DataTable dataTable = new DataTable(typeof(T).Name);
        //Get all the properties
        PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (PropertyInfo prop in Props)
        {
            //Setting column names as Property names
            dataTable.Columns.Add(prop.Name);
        }
        foreach (T item in items)
        {
            var values = new object[Props.Length];
            for (int i = 0; i < Props.Length; i++)
            {
                values[i] = Props[i].GetValue(item, null);
            }
            dataTable.Rows.Add(values);
        }
        DataSet ds = new DataSet();
        ds.Tables.Add(dataTable);
        return ds;
    }

    public ActionResult Logout()
    {
        HttpContext.Session.Clear();
        return RedirectToAction("Login", "Account");
    }
}

Validate Method is called when we click on the Login button on the Login Page. It Generates the Menu HTML based string keep in the Session. 

How It will work

On Successful Login, First of all, get the Role ID of the Admin, from Role ID get all menus list from Database which is allowed to the User & display in your Application Menu. Also, store in your Sessions memory.

Use ActionFilterAttribute to check before every Action that either this Action is Valid for the Logged in User or Not.

Here’s the Code for ActionFilterAttribute

public class AuthorizedAction: ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        
    }

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

        if (filterContext.HttpContext.Session.GetString("email") == null)
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary { { "controller", "Account" }, { "action", "Login" } });
            return;
        }

        var menus = JsonConvert.DeserializeObject<List<Menus>>(filterContext.HttpContext.Session.GetString("menus"));
        var controllerName = filterContext.RouteData.Values["controller"];
        var actionName = filterContext.RouteData.Values["action"];
        string url = "/" + controllerName + "/" + actionName;

        if (!menus.Where(s => s.Url == url).Any())

        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary { { "controller", "Account" }, { "action", "Login" } });
            return;
        }
    }
}

I would recommend using routing to not reload your whole page on every option-click.

Here’s the Angular UI-Router Tutorial with Asp.net Core.

I’ll recommend to Download Complete Code from GitHub & run it. In my opinion, If you are able to understand the Database, you can easily Implement the solution in your own way.

Download Complete Code

If you want any support from me please comment below. I’m always available to help you.

FAQ.

Q: How to Authorize Links other than Menu?

Ans: Add another column in the “Menus” Table as “is_menu” & when getting menus list on Successful login, filter the records only have “is_menu” = 1. Rest of the work will be as explained above.

Now you can Add all links(other than menu items), you want to authorize in “Menus” Table.

You might be interested in:

User’s Activity Logging

7 COMMENTS

  1. So essentially you are making a predefined database structure that has a filter table like. Now rather than editing source code to add authorizations we submit new roles via Entity framework to the database as entries and our “filter-system” already Enforces the Authorizations, correct?

  2. Hi Shehryar, great article thanks mate. I have been trying to build a MVC app just like this for a few days and your article really helped – Thankyou. I also wouldn’t mind seeing the code, just to see how a pro does it 🙂
    Thanks again, great article. Steve

LEAVE A REPLY

Please enter your comment!
Please enter your name here