Skip to content


Yii Framework Access Control Lists

In my series Learning the Yii Framework, I discuss the individual parts of the MVC (Model, View, Controller) architecture in some detail, from a Yii perspective. In the post on Controllers, I introduce Access Control Lists (ACLs), Yii’s default way of restricting who can take what actions. This is a key part of the security of any Web application. For example, a site’s content can often be read by anyone at all, registered or non-registered users alike (like the text you’re reading now). Some content may only be viewable by registered users and some by registered users of a certain type (e.g., paid members). Finally, some content may only be viewable by administrators. In this post, I detail how to completely control access to your Web application using Yii’s Access Control Lists.To start by repeating what I wrote in my Basic Controller Edits post, a Controller’s accessRules() method dictates who can do what. In a very simple way, the “what” refers to the Controller’s action methods, like actionList() or actionDelete(). In other words, only X type of user can call the actionDelete() method, which, of course, deletes a record. Your “who” depends upon the situation, but to start there’s at least logged-in and not logged-in users, represented by * (anyone) and @ (logged-in users), accordingly. Depending upon the login system in place, you may also have levels of users. So the accessRules() method uses all this information and returns an array of values. The values are also arrays, indicating permissions (allow or deny), actions, and users:

public function accessRules()
{
    return array(
        array('allow',  // allow all users to perform 'list' and 'show' actions
            'actions'=>array('list','show'),
            'users'=>array('*'),
        ),
        array('allow', // allow authenticated user to perform 'create' and 'update' actions
            'actions'=>array('create','update'),
            'users'=>array('@'),
        ),
        array('allow', // allow admin user to perform 'admin' and 'delete' actions
            'actions'=>array('admin','delete'),
            'users'=>array('admin'),
        ),
        array('deny',  // deny all users
            'users'=>array('*'),
        ),
    );
}

That’s the default setting for a Controller, where anyone can perform list and show actions, meaning that anyone can list all records or show individual records in an associated Model. The next section allows any logged-in user to perform create and update actions. Next, only administrators can perform admin and delete actions. Finally, a global deny for all users is added, to cover any situation that wasn’t explicitly defined. This is just a good security practice. Note that these rules just apply to this Controller; each Controller needs its own rules.

So at the most basic level, your access rules begin by allowing or denying actions to logged-in or non-logged-in users. When you go to create your own rules, start with what anyone can do and end with what no one can do. For example, say you have some user management system that includes the Models User and UserType, corresponding to related tables in the database. The UserType would be a simple two-field object—id and type, where type might be reader, writer, editor, and so forth. Each User then is assigned a single, specific type. I’d be inclined to grant list and show permissions to everyone, as that might be useful for browsing users by type and would be necessary for adding new users. However, I probably wouldn’t allow create, update, admin (which is really a variation on list), or delete permissions on UserType at all, with the thinking that these are static values. The accessRules() would then be:

public function accessRules()
{
    return array(
        array('allow',  // allow all users to perform 'list' and 'show' actions
            'actions'=>array('list','show'),
            'users'=>array('*'),
        ),
        array('deny', // no one can create, update, or delete these:
            'actions'=>array('create','update','admin','delete'),
            'users'=>array('*'),
        ),
        array('deny',  // deny all users anything not specified
            'users'=>array('*'),
        ),
    );
}

As you can see, just to be extra careful, I include a final deny clause still: if actions are added later the default behavior will be denial; only by then changing the rules can that action be executed by anyone.

To take this beyond logged-in vs. non-logged-in users, you need to know that the representation of logged-in users relies upon Yii’s authentication components. In the default rules, the admin user can perform certain actions, where “admin” is the actual name of the user that is logged in, and comes from protected/components/UserIdentity.php.  I write about the authentication process in detail in two posts: the first covering simple authentication, the second covering more evolved authentication. The default Yii application allows for two users, with names of demo and admin, but you’d likely have a more elaborate system in place. If you know only a limited number of users will be admins, you could hardcode their usernames into the rules:

array('allow', // allow harold and maude user to perform 'admin' and 'delete' actions
    'actions'=>array('admin','delete'),
    'users'=>array('harold','maude'),
),

This is still a bit too static, though. Perhaps your users in the database would be registered by user type, or role, and that the permissions would be based upon these roles. In that case, you can add an expression element to the returned array in the access rules:

array('allow',
    'actions'=>array('admin','delete'),
    'users'=>array('@'),
    'expression'=>'PHP code to be evaluated'
),

Two things about this expression. First, you still need to use the users element, and you’ll probably want to still restrict this to logged-in users (most likely). Second, the expression itself should be some PHP code, quoted, that when evaluated gives a Boolean result. If the code in the expression will be true, then permission will be allowed; false, denied. Say you wanted to restrict the publish action to only those with the role of editor:

array('allow',
    'actions'=>array('publish'),
    'users'=>array('@'),
    'expression'=>'isset($user->role) && ($user->role==="editor")'
),

The $user->role value would have to be established when the user logs in, as part of the authentication process. I discuss this exact example in my second post that covers more evolved authentication. And I put the entire expression within quotes (it doesn’t matter whether you use single or double, so long as you don’t create a parse error). With that rule, users that aren’t logged in, or users that are logged in but have non-editor roles, won’t be able to take that action.

A final way in which I’ve enforced authorization in previous Yii projects involves a bit of a hack. I had a site where users with a certain role could create certain types of content. They could also update or delete those types of content, but only if they were the one to create that content in the first place. In other words, say a user creates an event, then their user ID gets associated with that event’s record. The update and delete actions in the EventController should only be executable if the currently-logged-in user’s ID is the same as the ownerId of the event in question. As far as I know, this cannot be addressed in the accessRules() method, because those rules are evaluated prior to the loading of any specific Model. My solution, perhaps a hack but it works, was to add the proper logic within the actions. Here’s a simplified version of how that would look within one of the actions:

public function actionDelete()
{
    $event = $this->loadEvents(); // Fetch the specific event Model.
    // Can only delete the events they created:
    if ($event->ownerId == Yii::app()->user->id) {
        $event->delete();
    } else {
        throw new CHttpException(403,'Invalid request. You are not allowed to delete this event.');
    }
}

As I said, that’s a hack but it works fine for me and is simple enough to follow (also, the actions that display events for editing and deleting get changed to only list those with a matching ownerId). A better solution would likely be to use Yii’s Role-Based Access Control (RBAC). I haven’t personally gone down that path yet as I haven’t had the need; the above knowledge has more than sufficed for the sites I’ve created thus far.

As always, thanks for reading and let me know what comments or questions you may have. Thanks, Larry

Posted in MySQL, PHP, Web Development.

Tagged with , , , , .


44 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. mlindhout says

    Hi Larry,

    Thanx for all your Yii posts so far, they helped me a lot. I just figured out the same yesterday about ACL’s, and I came up with this in my accessRules method, pretty the same but a bit more readable:


    ‘expression’=>’User::hasRole(User::ROLE_ADMIN)’,

    Anyway, thanks again and keep up the good posting…

    • Larry says

      Thanks for the nice words and for sharing your work. I assume, then, that the User Model has the ability to retrieve the currently-logged-in user and that the hasRole() method returns a Boolean value.

  2. mlindhout says

    Oh, and about your actionDelete access checks, I think is ok to do it that way. Yesterday I read all about the CAuthManager components, but that ends up as pretty heavy to implement and even then you end up with a thing like this:

    if(Yii::app()->user->checkAccess(‘updatePost’))
    {
    // update post
    }

    • Larry says

      Thanks again for sharing. If that’s their solution, it’s pretty close to what I end up doing anyway.

    • Klaus says

      I think you often needs to check whether the logged in user is the owner of a post.
      And when you only need this as role management it is better do it without rbac because you must do the same then mindhout says.
      And when you have for example a task updateOwnPost and check only this there are 3 queries needed. When you check only for an operation like update then there are needed 6 or seven queries to check it.
      Read the rbac docs.
      For simple said I agree with Larry. When you not need a complex role system wiht many of roles tasks and operations it is better to do this wiht ACL.

  3. Ciudad Redonda says

    Thanks for the tip, I’ve problems with yii 1.1 accessRules.

  4. Scott DePouw says

    In your second code snippet you have the line

    array(‘allow’, // no one can create, update, or delete these:

    Shouldn’t that read ‘deny’ instead of ‘allow’?

    • Larry says

      D’oh! Thank you for pointing that out. I fixed it.

  5. Andreas says

    Very helpful, exactly what I was looking for – thanks a lot!

  6. Shamit Kumar Tomar says

    Thanks. I spent two days in Yii RBAC, sRBAC and what nots. But, finally your method works like a charm and is much easier and faster.

    • Larry says

      Glad to hear it. Thanks for letting me know.

  7. Gerard says

    Hey Larry,

    I just want to say thank you. I’m starting out in yii and your site is a better resource for me than the yii documentation.

    IGerard

    • Larry says

      Thanks, Gerard. Good luck with your Yii studies!

  8. cah cilik says

    complicated.. thx 4 share

  9. tyforsmoking says

    thank you so much for this post,.

  10. Maurice says

    Hi Larry,

    My situation is the same as Gerard above. Your writings have been far more useful. Thank you very much. Keep up the good work. You are really talented in it.

    Maurice
    shalomsoftware.com.au

    • Larry says

      Thanks for the nice words. I’m hoping to do a book on the Yii framework later this year.

  11. Davor says

    While searching the web in trying to understand how to use Yii RBAC (for some time) your wonderful simplification that actually works popped up in the search results. Thank you so much!

  12. greg says

    Thanks Larry! that put me right into what i wanted to do and explained a lot. Cheers from Poland!

  13. rene says

    Hi larry , i have this code and don´t filter good the users

    return array(
    array(‘allow’, // allow all users to perform ‘index’ and ‘view’ actions
    ‘actions’=>array(‘index’),
    ‘users’=>array(‘*’),
    ),
    array(‘allow’, // allow authenticated user to perform ‘create’ and ‘update’ actions
    ‘actions’=>array(‘update’,'view’),
    ‘expression’=>’(Yii::app()->user->getState(“idTipo”)==1)’
    ),
    array(‘allow’, // allow admin user to perform ‘admin’ and ‘delete’ actions
    ‘actions’=>array(‘admin’,'delete’,'create’),
    ‘expression’=>’(Yii::app()->user->getState(“idTipo”)==2)’,

    ),
    array(‘deny’, // deny all users
    ‘users’=>array(‘?’),
    ),
    );

  14. iGooodman says

    @Larry thanks, Larry.
    this post solved my problem.thanks.

  15. Joyce says

    Hi Larry, am using yii_1.1.7. but when i tried to edit UserIdentity.php in order to enable users from the DB to login, it gives me Incorrect username or password error whenever am loging in. I’ve tried this for three days and have failed what can i do?

    • Larry says

      I would recommend turning to either my support forum or the Yii support forum, providing more details in the process.

  16. Murtaza says

    Just want to say, your are great write. I have learned many thing from your write ups. I have only one questions which is still confusing and i don’t know how to make that efficiently and that is how to make the admin panel and main website separate and how can i make the theme only for admin panel?

    • Larry says

      Thanks for the nice words on the writing. In answer to your question, you’ll want to create a “module”, which is like a separate Yii application.

  17. Jim says

    Being an old Fusebox, CodeIgniter, and Zend Framework guy I was ready to learn something new. Picked up Yii and have been working on a pet project with it. I’ve read the Agile Yii book and what’s so far been released of the Yii 1.1 RAW cookbook. Learned a lot but still trying to get the feel. I must say though, what a treat it was to find your site. Your articles are so concise and spot on. Not to mention the fantastic prose! Thank you so much for having to passion to take the time and share with the rest of us.

    • Larry says

      Hello Jim,
      Thanks for the nice words on my work. I’m glad you liked it and it’s kind of you to say so.

      Cheers,
      Larry

  18. Bonnie says

    Thanks Larry this has really helped me.

    • Larry says

      You’re quite welcome. Thanks for the nice words.

  19. bonnie says

    Larry just a question because of some minor confusions I have controller with an actionAdmin so in my app I allow all authenticated users to do admin tasks but load only the items that belongs to that user. I have tried to right a code within the actionAdmin to load the users item based on the logged in user id but does work. how can i go about by modifying yii’s default actionAdmin.

    • Larry says

      The short answer is you just edit the method. The long answer is I don’t understand exactly what you mean and I’d prefer that questions like this, which would require longer replies and more replies and more replies and code not be done through the comments to a blog post. Please use my support forum or the Yii support forum if you need help.

  20. MetaCrawler says

    Very nice post! Helped me a lot! :) THANKS!

    It is much easier to understand than the yii role-based-acces-control article on yiiframework.com …
    I already spent a lot of time on the rbac tutorial… and I still have some understanding problems.
    I read your post only one time and understood everything.

    But I have a little question:
    What is the “advantage” of using the “Yii-RBAC” instead of your solution?
    Your solution is much easier, less work… and it does the same thing… (or not?)

    Best Regards from germany! :)

    • Larry says

      Thanks for the nice words. Glad you liked it. As for your question, well, my solution is a bit of a hack, as I said, and hacking framework (i.e., standardized) code isn’t really a good thing. And the RBAC allows finer control. I’m not saying you should rush out to use RBAC, but it has its merits, of course.

  21. Nathan says

    Hello,
    Regarding you last example (checking if the event being deleted belongs to the user), what if the relationship is MANY_MANY, that is, I don’t have a “ownerId” but an array of owners?

    I could I guess iterate over the values of the array but I’m wondering if there is a Yii-way of doing this.

    • Larry says

      Well, you could just use in_array(). That’s not a “Yii-way” necessarily, but it’d only take a line of code.

  22. Alex says

    Hi Larry! Thanx again for the great post! Just to get everything ordered in future – can you explain why somewhere is used === instead of == and :: instead of -> ?…

    • Larry says

      Hello Alex. === is an identical comparison; == is an equality comparison. It’s best to make identical comparisons in situations where you want to distinguish between true-like values and actual true, or false-like values (such as 0) and actual false. :: is the scope resolution operator, used to refer to a member of a class. -> is for referring to a member of an object. None of these are particular to frameworks; they’re part of PHP proper.

  23. Icarus A says

    Larry, your tutorial series on Yii is really a great resource for the community, and more so for people just beginning on Yii, since these are specific use-case based deep dives.

    Wondering if your method would be a good natural fit to where several thousand (maybe couple of hundred thousand) users, each having their own “private” content. E.g.

    user0001 has access to content0001a, content0001b, content0001c…
    user0002 has access to content0002a, content0002b, content0002c…

    user000N has access to content000Na, content000Nb, content000Nc…

    although all users, do have access to some limited shared / common content as well. The content in question is of sensitive nature (e.g. scanned tax filing images etc.), so the ACL needs to be pretty much bullet proof.

    Would the ACL method scale well as per this use case ?

    • Larry says

      It sounds like going the more formal route of an RBAC would seem to make the most sense. I think of ACL as being a relatively simple approach and the problem is it needs to be hardcoded into each controller, which would be unmanageable in your case, I suspect. Thanks for the nice words!

Continuing the Discussion

  1. Larry Ullman's Blog » Forcing Login for All Pages in Yii linked to this post on July 20, 2010

    [...] time back, I had written a couple of blog posts on authentication and authorization in Yii. As a comment to one of those posts, someone shared some code (also posted in the Yii [...]

If you need quick assistance with a question or problem related to one of my books, please use the support forums instead.

Some HTML is OK

or, reply to this post via trackback.