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.
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 @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@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 CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_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.
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:
I am working on this admin dashboard. I am stuck into the calendar design. Not able to find out the working process. Please share how the calendar works with a live example. Or please share some reference.
Thanks
Hi, please explain what do you want to achieve? are you trying yo select the date using calender?
Thanks for this article, But I know about Identity in razor page
i have done all thing that is mentioned in your code but i m struggling with one problem is that :
In my code have a dropdown in a form and it bind from database so to open that form then i have to save form url as well as dropdown url while assigning authority to Role.
Please suggest me the solution for this
QSL Script is available above.
I am having a problem creating the database in SQL server, do you have a script for that?