Yii2 – Custom Role Based Access Control
Authorization is the process of verifying that a user has enough permission to do something. Yii provides two authorization methods: Access Control Filter (ACF) and Role Based Access Control (RBAC).
This tutorial will show you how you can create Custom-Role Based Access Control (C-RBAC) without using migrations
and authManager
. Previously I had written a post regarding how to program, Basic User Login From Database in an Yii2 application. This previous post was continue to login with Role Based Access Control.
A role represents a collection of permissions (e.g. create posts, view post, update posts & delete post). A role may be assigned to one or multiple users. To check if a user has a specified permission, we may check if the user is assigned with a role that contains that permission.
Implementing a role based access control is a very easy process using CRUD (without using migrations
and authManager
) and you can even load your roles from the database if you want.
Step1: Creating necessary tables in the database [without using migration]
The first step is to create necessary tables in the database. Below is the sql you need to run in the database.
uses four database tables to store its data:
- modulesTable: the table for storing all modules items. Defaults to “
modules_list
“. - rolePermissionTable: the table for storing authorization item hierarchy. Defaults to “
role_module_permission
“. - userTable: the table for storing authorization modules assignments. Defaults to “
user
“. - roleTable: the table for storing roles. Defaults to “
role_types
“.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
-- phpMyAdmin SQL Dump -- version 4.6.4 -- https://www.phpmyadmin.net/ -- -- Host: 127.0.0.1 -- Generation Time: Jun 21, 2017 at 03:20 PM -- Server version: 5.7.14 -- PHP Version: 5.6.25 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; -- -- Database: `role_based_access_control` -- -- -------------------------------------------------------- -- -- Table structure for table `modules_list` -- CREATE TABLE `modules_list` ( `module_id` int(11) NOT NULL, `module_name` varchar(100) DEFAULT NULL, `controller` varchar(50) NOT NULL, `icon` varchar(25) NOT NULL, `is_active` tinyint(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Dumping data for table `modules_list` -- INSERT INTO `modules_list` (`module_id`, `module_name`, `controller`, `icon`, `is_active`) VALUES (1, 'Roles', 'role', 'fa-user-secret', 1), (2, 'Admins', 'admin', 'fa-user', 1), (3, 'Languages', 'languages', 'fa-language', 1), (4, 'Country Lists', 'country-list', 'fa-globe', 1), (5, 'Genre Masters', 'genre-master', 'fa-magic', 1), (6, 'Channel List', 'channel-list', 'fa-tv', 1), (7, 'Series List', 'series-list', 'fa-camera-retro', 1), (8, 'Video List', 'video-list', 'fa-video-camera', 1); -- -------------------------------------------------------- -- -- Table structure for table `role_module_permission` -- CREATE TABLE `role_module_permission` ( `id` int(11) NOT NULL, `role_id` int(11) DEFAULT NULL, `module_id` int(11) DEFAULT NULL, `new` tinyint(4) NOT NULL COMMENT 'CREATE', `view` tinyint(4) NOT NULL COMMENT 'READ', `save` tinyint(4) NOT NULL COMMENT 'UPDATE', `remove` tinyint(4) NOT NULL COMMENT 'DELETE', `added_at` datetime DEFAULT NULL, `added_by` varchar(45) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Dumping data for table `role_module_permission` -- INSERT INTO `role_module_permission` (`id`, `role_id`, `module_id`, `new`, `view`, `save`, `remove`, `added_at`, `added_by`) VALUES (1, 1, 1, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (2, 1, 2, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (3, 1, 3, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (4, 1, 4, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (5, 1, 5, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (6, 1, 6, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (7, 1, 7, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (8, 1, 8, 1, 1, 1, 1, '2017-06-20 16:57:10', '1'), (9, 2, 4, 1, 1, 0, 0, '2017-06-21 08:40:51', '1'), (10, 2, 5, 1, 1, 0, 0, '2017-06-21 08:40:51', '1'), (11, 2, 6, 0, 1, 0, 0, '2017-06-21 08:40:51', '1'), (12, 2, 7, 0, 1, 0, 0, '2017-06-21 08:40:51', '1'), (13, 2, 8, 0, 1, 0, 0, '2017-06-21 08:40:51', '1'); -- -------------------------------------------------------- -- -- Table structure for table `role_types` -- CREATE TABLE `role_types` ( `role_id` int(11) NOT NULL, `role_name` varchar(100) DEFAULT NULL, `is_active` tinyint(1) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Dumping data for table `role_types` -- INSERT INTO `role_types` (`role_id`, `role_name`, `is_active`) VALUES (1, 'Admin', 1), (2, 'Moderate', 1); -- -------------------------------------------------------- -- -- Table structure for table `user` -- CREATE TABLE `user` ( `id` int(11) NOT NULL, `first_name` varchar(250) NOT NULL, `last_name` varchar(250) NOT NULL, `phone_number` varchar(30) NOT NULL, `username` varchar(250) NOT NULL, `email` varchar(500) NOT NULL, `password` varchar(250) NOT NULL, `authKey` varchar(250) NOT NULL, `password_reset_token` varchar(250) NOT NULL, `user_image` varchar(500) NOT NULL, `user_level` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- -- Dumping data for table `user` -- INSERT INTO `user` (`id`, `first_name`, `last_name`, `phone_number`, `username`, `email`, `password`, `authKey`, `password_reset_token`, `user_image`, `user_level`) VALUES (1, 'admin', 'admin', '', 'admin', 'prakash@keyslab.com', '01cfcd4f6b8770febfb40cb906715822', '54321', '', '', 1), (2, 'Prakash', 'Prakash', 'Prakash', 'moderate', 'prakashsmartt@gmail.com', '827ccb0eea8a706c4c34a16891f84e7b', '', '', '', 2); -- -- Indexes for dumped tables -- -- -- Indexes for table `modules_list` -- ALTER TABLE `modules_list` ADD PRIMARY KEY (`module_id`); -- -- Indexes for table `role_module_permission` -- ALTER TABLE `role_module_permission` ADD PRIMARY KEY (`id`), ADD KEY `fk_module_id_idx` (`module_id`), ADD KEY `fk_role_id_idx` (`role_id`); -- -- Indexes for table `role_types` -- ALTER TABLE `role_types` ADD PRIMARY KEY (`role_id`); -- -- Indexes for table `user` -- ALTER TABLE `user` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `username` (`username`), ADD KEY `user_level` (`user_level`); -- -- AUTO_INCREMENT for dumped tables -- -- -- AUTO_INCREMENT for table `modules_list` -- ALTER TABLE `modules_list` MODIFY `module_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; -- -- AUTO_INCREMENT for table `role_module_permission` -- ALTER TABLE `role_module_permission` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=14; -- -- AUTO_INCREMENT for table `role_types` -- ALTER TABLE `role_types` MODIFY `role_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; -- -- AUTO_INCREMENT for table `user` -- ALTER TABLE `user` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; -- -- Constraints for dumped tables -- -- -- Constraints for table `role_module_permission` -- ALTER TABLE `role_module_permission` ADD CONSTRAINT `fk_module_id` FOREIGN KEY (`module_id`) REFERENCES `modules_list` (`module_id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `role_types` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE; -- -- Constraints for table `user` -- ALTER TABLE `user` ADD CONSTRAINT `user_ibfk_1` FOREIGN KEY (`user_level`) REFERENCES `role_types` (`role_id`) ON DELETE SET NULL ON UPDATE CASCADE; |
Add Modules into Table:
Create CRUD for Roles:
Set Permission for each Roles:
View Role Permissions:
Assign role to User:
Step2: Create components file as ModulesPermission
In this ModulesPermission
you can get menus, roles and permissions. Because most access check is about the current user. This is done by adding the following files to the components folder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<?php /** * @author Prakash S * @copyright 2017 */ namespace app\components; use Yii; use yii\base\Component; use yii\base\InvalidConfigException; class ModulesPermission extends Component { public function getMenus() { $role_id = Yii::$app->user->identity->user_level; $modules = \app\models\RolePermission::find() ->select('ML.module_name, ML.controller, ML.icon') ->join('LEFT JOIN', 'modules_list AS ML', 'ML.module_id = role_module_permission.module_id') ->where('role_id = :role_id', [':role_id' => $role_id]) ->andWhere('role_module_permission.view = :view', [':view' => 1]) ->andWhere('is_active = :is_active', [':is_active' => 1]) ->asArray()->all(); $items = array(); $items[] = '<li class="header">Menu</li>'; $items[] = '<li id="dashboard"><a href="'. \yii\helpers\Url::to(['/']).'"><span class="fa fa-dashboard"></span> Dashboard</a></li>'; for($i=0; $i<count($modules); $i++) { $items[] = '<li id="'.$modules[$i]['controller'].'"><a href="'. \yii\helpers\Url::to([$modules[$i]['controller'].'/']).'"><span class="fa '.$modules[$i]['icon'].'"></span> '.$modules[$i]['module_name'].'</a></li>'; } return $items; } public function getPermission() { $actions = array(); $actions['index'] = 'view'; $actions['view'] = 'view'; $actions['create'] = 'new'; $actions['update'] = 'save'; $actions['delete'] = 'remove'; $role_id = Yii::$app->user->identity->user_level; $action = $actions[Yii::$app->controller->action->id]; $controller = Yii::$app->controller->id; $permission = \app\models\RolePermission::find() ->select($action) ->join('LEFT JOIN', 'modules_list AS ML', 'ML.module_id = role_module_permission.module_id') ->where('role_id = :role_id', [':role_id' => $role_id]) ->andWhere($action.' = :'.$action, [':'.$action => 1]) ->andWhere('controller = :controller', [':controller' => $controller]) ->one(); return $permission[$action] ? true : false; } } ?> |
Step3: Setting up the config file
Now you can set up the config file to use the components as ModulesPermission
. This is done by adding the following lines to the components section of your config file.
1 2 3 4 5 |
'components' => [ 'Permission' => [ 'class' => 'app\components\ModulesPermission', ], ], |
Step4: Get menus access
Get current user menu access using Yii::$app->Permission->getMenus()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php use yii\bootstrap\Nav; ?> <aside class="main-sidebar" style="padding-top: 0px;"> <section class="sidebar"> <?= Nav::widget( [ 'encodeLabels' => false, 'options' => ['class' => 'sidebar-menu'], 'items' => Yii::$app->Permission->getMenus(), ] ); ?> </section> </aside> |
Step5: Check role based access control
With the authorization data ready, access check is as simple as a call to the beforeAction
method. Because most access check is about the current user, for convenience Yii provides a shortcut method Yii::$app->Permission->getPermission()
, which can be used like the following in all controllers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
......... ......... use yii\filters\AccessControl; class ListController extends Controller { public function behaviors() { return [ 'access' => [ 'class' => AccessControl::classname(), 'rules' => [ [ 'allow' => true, 'roles' => ['@'] ] ] ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'delete' => ['post'], ], ], ]; } public function beforeAction($event){ if(Yii::$app->Permission->getPermission()) return parent::beforeAction($event); else $this->redirect(['site/permission']); } ........ ........ } |
Step6: Add permission action in site controller.
1 2 3 4 |
public function actionPermission() { return $this->render('denied'); } |
Step7: Create access denied error page in site folder as denied
file name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="site-error"> <div class="alert alert-danger"> <span class="fa fa-warning"></span> Access is denied! </div> <p> You don't currently have permission to access this module. </p> <p> Please refer to your system administration. Thank you. </p> </div> |