CakePHP Auth Component - Users, Groups & Permissions

Published: on 20/5/08 | Comments (32)

UPDATE:

following on from the great response I have received to this article and numerous suggestions and requests left in the comments, I have now published an updated version of this article, which you can find here, the updated version contains all the code found here plus it has been refactored and improved to add features such as CakePHP 1.2.0.7125RC1 support, along with Permissions Caching and Controller Wide Permissions Wildcard support.

Ok, I've been using CakePHP for about three years now, and I am hooked, it has not only helped me build applications quicker and more efficiently for my clients, but also allowed me to concerntrate on writing the business logic rather than getting bogged down with structural plumbing and security.

So, I thought it was high time to give something back to the community and here it is with my first ever tutorial. Hopefully you'll find it usefull and leave a few comments, or better still, click the Share this link at the bottom of the article to help spread the word.

I'm going to walk you through creating a complete Authentication and Authorization system using CakePHP's Auth component, the tutorial was built using CakePHP 1.2.0.6311 beta, and allows for Users to belong to multiple Groups and for each Group to have multiple Permissions, without going anywhere near the ACL component.

So, without further ado, heres the code:

The Database

You can of course extend this in any way you choose for your application, but I will give you the basics needed to get the system up and running:

CREATE TABLE users (
    id int (11) NOT NULL auto_increment,
    email_address varchar (50) NOT NULL,
    password varchar (50) NOT NULL,
    active tinyint(1) NOT NULL,
    created datetime NOT NULL,
    modified datetime NOT NULL,
        PRIMARY KEY (id)
);
CREATE TABLE groups (
    id int(11) NOT NULL auto_increment,
    name varchar(50) NOT NULL,
    created datetime NOT NULL,
    modified datetime NOT NULL,
    PRIMARY KEY (id)
);
CREATE TABLE permissions (
    id int(11) NOT NULL auto_increment,
    name varchar(50) NOT NULL,
    created datetime NOT NULL,
    modified datetime NOT NULL,
    PRIMARY KEY (id)
);
CREATE TABLE groups_users(
    group_id int(11) NOT NULL,
    user_id int(11) NOT NULL
);
CREATE TABLE groups_permissions(
    group_id int(11) NOT NULL,
    permission_id int(11) NOT NULL
);

The Models

User Model

app/models/user.php

class User extends AppModel {
var $displayField = 'email_address'; var $name = 'User'; var $useTable = 'users'; var $validate = array( 'email_address' => array('email'), 'password' => array('alphaNumeric'), 'active' => array('numeric') ); var $hasAndBelongsToMany = array( 'Group' => array('className' => 'Group', 'joinTable' => 'groups_users', 'foreignKey' => 'user_id', 'associationForeignKey' => 'group_id', 'unique' => true ) ); }

Group Model

app/models/group.php

class Group extends AppModel {
    var $name = 'Group';
    var $useTable = 'groups';
    var $hasAndBelongsToMany = array(
            'Permission' => array('className' => 'Permission',
                        'joinTable' => 'groups_permissions',
                        'foreignKey' => 'group_id',
                        'associationForeignKey' => 'permission_id',
                        'unique' => true
            ),
            'User' => array('className' => 'User',
                        'joinTable' => 'groups_users',
                        'foreignKey' => 'group_id',
                        'associationForeignKey' => 'user_id',
                        'unique' => true
            )
    );
}

Permissions Model

app/models/permission.php

class Permission extends AppModel {
    var $name = 'Permission';
    var $useTable = 'permissions';
    var $hasAndBelongsToMany = array(
            'Group' => array('className' => 'Group',
                        'joinTable' => 'groups_permissions',
                        'foreignKey' => 'permission_id',
                        'associationForeignKey' => 'group_id',
                        'unique' => true
            )
    );
}

The Controllers

Users Controller

app/controllers/users_controller.php

class UsersController extends AppController {
    var $name = 'Users';
    var $scaffold;
    function login(){}
    function logout(){
        $this->redirect($this->Auth->logout());
    }
}

Groups Controller

app/controllers/groups_controller.php

class GroupsController extends AppController {
    var $name = 'Groups';
    var $scaffold;
}

Permissions Controller

app/controllers/permissions_controller.php

class PermissionsController extends AppController {
    var $name = 'Permissions';
    var $scaffold;
}

App Controller

app/app_controller.php

class AppController extends Controller {
    var $admin = array();
    var $allowedActions = null;
    var $components = array('Auth');
    function beforeFilter(){
        //Override default fields used by Auth component
        $this->Auth->fields = array('username'=>'email_address','password'=>'password');
        //Set application wide actions which do not require authentication
        $this->Auth->allow('*');
        //Set the default redirect for users who logout
        $this->Auth->logoutRedirect = '/';
        //Set the default redirect for users who login
        $this->Auth->loginRedirect = '/';
        //Extend auth component to include authorisation via isAuthorized action
        $this->Auth->authorize = 'controller';
        //Restrict access to only users marked as active
        $this->Auth->userScope = array('User.active = 1');
        //Pass auth component data over to view files
        $this->set('Auth',$this->Auth->user());
    }
    function beforeRender(){
        //If we have an authorised user logged in then set up the admin element
        if($this->Auth->user()){
            $controllerList = Configure::listObjects('controller');
            foreach($controllerList as $controllerItem){
                if($controllerItem <> 'App'){
                    if($this->__permitted($controllerItem,'index')){
                        $this->admin[] = $controllerItem;
                    }
                }
            }
        }
        $this->set('admin',$this->admin);
} function isAuthorized(){ return $this->__permitted($this->name,$this->action); } function __permitted($thisController,$thisAction){ //Ensure checks are all made lower case $thisController = low($thisController); $thisAction = low($thisAction); //Firstly users:logout is never restricted so set up a bypass if($thisController.':'.$thisAction == 'users:logout'){ return true; } //Now if allowedActions has not yet benn created, create it if(!$this->allowedActions){ //Import the User Model so we can build up the permission cache App::import('Model', 'User'); $thisUser = new User; //Now bring in the current users full record along with groups $thisGroups = $thisUser->find(array('User.id'=>$this->Auth->user('id'))); $thisGroups = $thisGroups['Group']; foreach($thisGroups as $thisGroup){ $thisPermissions = $thisUser->Group->find(array('Group.id'=>$thisGroup['id'])); $thisPermissions = $thisPermissions['Permission']; foreach($thisPermissions as $thisPermission){ $this->allowedActions[]=$thisPermission['name']; } } } //Now check the cache to see if we have permission set foreach($this->allowedActions as $allowedAction){ if($allowedAction == '*'){ return true;//Super Admin Bypass Found } if($allowedAction == $thisController.':'.$thisAction){ return true;//Specific permission found } } return false; } }

The Views

The login page

app/views/users/login.ctp

echo $form->create('User', array('action' => 'login'));
echo $form->input('email_address',array('between'=>'
','class'=>'text')); echo $form->input('password',array('between'=>'
','class'=>'text')); echo $form->end('Sign In');

Setting the system up

Assuming you have this set up on localhost:

Now change the line of code in app_controller.php that reads:

    $this->Auth->allow('*');

to read:

    $this->Auth->allow('display');

Now save app_controller.php and refresh your browser, and you should now find you need to log in to access the system. That's it, you now have the basics of an extremely flexible and powerfull Authentication and Authorization system.

Basic Useage

Now you have the system in place, it's pretty easy to use, so here is a quick run through:

Permissions

By default any action named display is not protected, all other actions are.

To grant permission to any group to access an action other than display, add a new permission in the format controller:action and add it to the group for which you wish to grant access.

To allow unristricted access to other actions, for example if you wish to add an open action at users/home, add the following code to your users_controller.php file.

    function beforeFilter(){
        parent::beforeFilter();
        $this->Auth->allow('display','home');
    }

This will inherit and override the basic beforeFilter adding the new action. (Don't forget to include any requestAction functions here that you may call in your elements).

Detecting users within your view files.

If a valid user is logged into the system a variable called $Auth is passed to the view in the app_controller:beforeFilter so, if for example you wish to display a link in any of your views or elements to log the current user out, simply do this:

if(isset($Auth)){
    echo ' | '.$html->link('Logout',array('controller'=>'users','action'=>'logout'))."\n";
}

Try using:

    debug($Auth);

within any of you views to find out all the information you have at your disposal within your views.

An Admin menu for free

As a bonus, the application passes over a variable to view files called $admin, which you can test within your view files or elements, if it's not empty, then the currently logged in user has permission to access some controller:index actions and you can then iterate through the $admin array to create a list of admin links.

Wrapping up

Well that's about all for now, if you don't wont to enter all the code yourself, Download the code here. In a future article I will expand on this to show you a pretty cool admin menu bar I've used in a couple of applications but for now, I hope you've found this usefull, till next time...

happy baking!

UPDATE

Just added the next instalment to this series where I show you how to extend this system with an automatically generated admin menu.

Comments

This post is now closed to new comments