RBAC Super Simple with Admin and User

One of the common requests I see in the forum is how to implement RBAC. While you can implement Yii 2's built-in RBAC, that might be too much for developers who are just starting with Yii 2 or have simpler needs. Sometimes you are looking for a fast solution and just want two flavors, user and admin. And even if you will eventually need more, you can use these methods as a starting point for developing your own features or move on to Yii 2's RBAC.

So this is a variation on my own implementation which is more involved, but this will get you the basics quickly. Using Yii 2's advanced template, 99.9 % of the work is done before you start. So here is what we'll do:

  1. add constants to the User model for admin and user role & add a role column on user table, type int, not null, default value 10.
  2. add the contstants into the range of values for user roles
  3. create a static method on the User model to check isUserAdmin
  4. create a loginAdmin method on the LoginForm model
  5. change the backend site controller to use loginAdmin method on login
  6. add an access control rule in the behaviors method to the frontend site controller to restrict access to the about page to logged in admin only

The last one I really love. We use Yii 2's behaviors to enforce our RBAC, the method for this is already built and intuitive. You will love how easy all this really is.

These instructions are for a fresh install of the advanced template, so it's assumed you have no existing code that will conflict. You can get the advanced template installation instructions here.

Ok, Step 1:

Add this to the top of your User model in common\models\User

const ROLE_USER = 10;
const ROLE_ADMIN = 20;

We use the constant to set the value of admin to 20. We are going to use the Role value on the user record to compare to this number. So, if the user's role is set to 20, they are admin. The default on signup is 10, so the only way for a user to get 20 on role at this point is for you to assign it directly in the DB (probably through PhpMyAdmin).

Since I wrote this tutorial orginally, Yii 2 dropped the role column from the advanced application in it's out-of-the-box build, so you will have to add a role column to your user table manually before continuing on. Make the role column int, not null, and default value = 10.

Step 2

Under the Rules method in the User model, add the following:

['role', 'default', 'value' => 10],
['role', 'in', 'range' => [self::ROLE_USER, self::ROLE_ADMIN]],

We add default rule and simply limit the allowed range of values for role to the two that we have defined in our constants.

Step 3

Add the following method at the bottom of the User model:

public static function isUserAdmin($username)
{
      if (static::findOne(['username' => $username, 'role' => self::ROLE_ADMIN])){
                        
             return true;
      } else {
                        
             return false;
      }
        
}

Ok, so we gave this method a very intuitive name. It looks up the user by the findOne method, where the $username is what we hand into the method and where the role is equal to the value we set on ROLE_ADMIN constant. If user is admin, we return true, if not, false.

We made it a static method, so we can keep the call to the method very concise and limit the clutter into other methods where we will use it.

Step 4

In common\models\LoginForm

Add the following method:

public function loginAdmin()
{
  if ($this->validate() && User::isUserAdmin($this->username)) {
    return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
  } else {
    return false;
  }
}

Simple, we are adding an additional condition to the if statement. Now, not only does it have to validate, but now User::isUserAdmin($this->username) has to evaluate to true.

Step 5

in backend\controllers\SiteController.php, change actionLogin to:

public function actionLogin()
{
   if (!\Yii::$app->user->isGuest) {
      return $this->goHome();
   }

   $model = new LoginForm();
   if ($model->load(Yii::$app->request->post()) && $model->loginAdmin()) {
      return $this->goBack();
   } else {
       return $this->render('login', [
          'model' => $model,
       ]);
   }
}

There is only one slight change between this code and the out-of-the-box, we are just using loginAdmin instead of login method.

Once you've made this change, any user trying to login to the backend will have to have a role value equal to that set by the ROLE_ADMIN constant on the User model. So now we just need to be able to enforce our access to the actions. There is a super simple way to do this using behaviors and the matchCallback method under rules.

I'm going to use the frontend about page as an example because the frontend site controller is already using access rules in it's behaviors method, so it's really easy to demonstrate how to use this.

Step 6

In frontend\controllers\SiteController.php, include the use statement at the top of the file:

use common\models\User;

Then modify the behaviors method in the same file, frontend\controllers\SiteController.php to:

public function behaviors()
{
   return [
       'access' => [
           'class' => AccessControl::className(),
           'only' => ['logout', 'signup', 'about'],
           'rules' => [
               [
                   'actions' => ['signup'],
                   'allow' => true,
                   'roles' => ['?'],
               ],
               [
                   'actions' => ['logout'],
                   'allow' => true,
                   'roles' => ['@'],
               ],
               [
                   'actions' => ['about'],
                   'allow' => true,
                   'roles' => ['@'],
                   'matchCallback' => function ($rule, $action) {
                       return User::isUserAdmin(Yii::$app->user->identity->username);
                   }
               ],
           ],
       ],
       'verbs' => [
           'class' => VerbFilter::className(),
           'actions' => [
               'logout' => ['post'],
           ],
       ],
   ];
}

So what we did that's different from what was already there. We modified: ~~~ 'only' => ['logout', 'signup', 'about'], ~~~

So now it applies to the about action as well. Then we added a block of rules in an array:

[
                        'actions' => ['about'],
                        'allow' => true,
                        'roles' => ['@'],
                        'matchCallback' => function ($rule, $action) {
                            return User::isUserAdmin(Yii::$app->user->identity->username);
                        }
                    ],

So this rule only applies to the about action. We use the matchCallback method, which needs to return true or false, to see if the current user, whom we have supplied by feeding in Yii::$app->user->identity->username, has a role of admin. If it returns true, the action is allowed, if not, it says You are not allowed to perform this action.

Obviously note that we also required logged in state for the about action because without the user being logged in, we can't do the call to determine the user's role.

This will work on any controller as long as you are implementing the behaviors method as described above. This means you can use this simple RBAC to control every action on the site, frontend or backend, if you wish.

So there you have it, super simple RBAC to get you started. Yii 2 does almost all the work before you even start. I've looked at many PHP frameworks and I've never seen one help you this much. We got so much power with so little code. If you want UI to set user's roles, just use Gii to create the user crud in the backend. Just remember to get the namespaces right, the core User model is in common\models.

Obviously, this is just a starter approach. If you just need two roles, this is fine. If you are doing an enterprise level application, you will probably need more, but you could still start with this to learn your way around Yii 2. I like to use the advance template because they give you a working user model out-of-the-box and you can see how quickly we were able to move along with that. I really love this framework.

6 1
14 followers
Viewed: 67 600 times
Version: 2.0
Category: Tutorials
Written by: evercode
Last updated by: evercode
Created on: Nov 9, 2014
Last updated: 3 years ago
Update Article

Revisions

View all history

Related Articles