Simple RBAC

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 a constant to the User model for admin role

  2. add the contstant 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.

[size="2"]Ok, step 1:[/size]

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




const ROLE_ADMIN = 20;



Step 2

Under the Rules method in the User model, change the in range rule of Role to the following:




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



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;

 		}

        

    }




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;

        }

    }



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

Modify the behaviors method in 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 [size="2"]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.[/size]

Also note, that on the controller, to do the check using the static call to User:: you will have to include the use statement at the top of the file:




use common\models\User;



[size="2"]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, if you wish.[/size]

[size=“2”]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.[/size]

[size="2"]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.[/size]

[size="2"]Ok, this was a long post. I will post to tutorials and to my blog as well. Just wanted to make it easier for new people to get up and running.[/size]

You are just ruining advanced template with such a bad code like this.

Thanks for your feedback, but you should be more specific with your objection. Also, keep in mind, it’s not meant to be an enterprise solution, it’s for lighter solutions.

The code has been tested and it works and is available for use now. It’s also getting thumbs up responses in the wiki, since it was posted yesterday. At least one person implemented it and said it works perfectly. Any questions or concerns, please let me know.

The full tutorial has a little more depth to it in terms of explanation, so you might want to check that out as well.