Note: This is for CakePHP 2.x, for 3.x please see the bottom of the article.
The CakePHP built in row based CRUD auth is way too powerful and way too slow and memory consuming.
In 99% of all cases there is no need for that. Also, I never really used the groups + roles of the core ACL. Basic groups usually do the trick.
If you just want to have some basic control over the access to specific actions, you need something else.
Here it comes.
Demo (NEW)
A live implementation can be found in my fun app cakefest.
Note how lean and clean the User model and the controllers are. Separation of concerns. DRY. Cool 😛
Preparations
Please make sure the Tools Plugin is properly loaded (see the plugin readme for details).
If you plan on using prefixed routing (admin, …), enable those in your core.php or bootstrap.php.
I assume you already got the AuthComponent included in the $components array of your AppController.
You probably also excluded all public views with something like
$this->Auth->allow('contact_form'); // in beforeFilter() of the specific controllers
This here (in the contact controller) makes Auth skip this action completely. The action will be accessible to everybody right away.
This is especially important for your login/register actions:
// UsersController
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('login', 'logout', 'register', ...);
}
Those actions should never trigger any Authorize module. All other actions then use our ACL to determine if access is granted or not.
You probably got a Role model (User belongsTo Role / User hasAndBelongsToMany Role) attached to the User.
If you don’t want this, use Configure to store your keys like so:
// in your config.php if applicable or using Configure::write('Role', array(...))
$config['Role'] = array(
// slug => identifier (unique magical number or maybe better a constant)
'superadmin' => 1,
'admin' => 2,
'moderator' => 3,
'helper' => 4,
'user' => 5,
);
You should at least have a user
– and maybe an admin
role – for it to make sense.
The advantage here: At any time you can switch from Configure to a Role model + roles table and vice versa without having to change much.
You must also have some kind of Authentication in your AppController:
$this->Auth->authenticate = array('Form'); // Uses username and password for login
Important: At least one type of authentication is necessary for any Authorize
module to be usable.
So far so good. You can login/logout and once you are logged in browse all non-public pages.
Even admin pages, of course. Thats where the TinyAuth class comes in.
TinyAuth
The code can be found at github.
First of all include it in your beforeFilter() method of the AppController:
$this->Auth->authorize = array('Tools.Tiny');
Alternatively, you could pass it as component settings right away:
public $components = [
...
'Auth' => [
'loginRedirect' => ...,
'logoutRedirect' => ...,
'authenticate' => ['Form'],
'authorize' => ['Tools.Tiny']
],
];
Now create a file in /Config/ called acl.ini like so:
[Tools.Countries]
* = superadmin ; this is a comment
[Account]
edit,change_pw = *
[Activities]
admin_index,admin_edit,admin_add,admin_delete = admin,superadmin
index = *
[Users]
index,search = user
* = moderator,admin
The format is normal PHP INI style. I already included all kind of examples. * is a placeholder for "any".
The plugin prefix for controllers is not necessary as of now (maybe for CakePHP 3 where the same controller name is allowed multiple times due to PHP5.3 namespaces).
Comments in INI files start with ";".
Explanations:
- Superadmin can access all Countries actions of the Tools plugin
- Account actions are accessible by all roles (and therefore logged in persons)
- Activities can be modified by all admins and listed by all (logged in persons)
- Users can search and list other users, but only moderators and admins have access to all other actions
That’s it. Really easy, isn’t it?
Some details
TinyAuth expects a Session Auth User like so:
Auth.User.id
Auth.User.role_id (belongsTo - role key directly in the users table)
or so:
Auth.User.id
Auth.User.Role (hasAndBelongsToMany - multi role array containing all role keys)
As you can see it can manage both single and multiple role setup.
That’s something the core one lacks, as well.
The current configuration is cached in the persistent folder by default. In development mode (debug > 0) it will be regenerated all the time, though. So remember that you have to manually clear your cache in productive mode for changes to take effect!
For more insight into the different role setups see this Wiki page.
Quicktips
If you have a cleanly separated user/admin interface there is a way to allow all user actions to users right away;
$this->Auth->authorize = array('Tools.Tiny' => array('allowUser' => true));
Only for admin views the authorization is required then.
If you got a "superadmin" role and want it to access everything automatically, do this in the beforeFilter method of your AppController:
$userRoles = $this->Session->read('Auth.User.Role');
if ($userRoles && in_array(Configure::read('Role.superadmin'), $userRoles)) {
// Skip auth for this user entirely
$this->Auth->allow('*'); // cake2.x: `$this->Auth->allow();` without any argument!
}
What about login/register when already logged in
That is something most are neither aware of, nor does the core offer a out-of-the-box solution.
Fact is, once you are logged in it is total nonsense to have access again to login/register/lost_pwd actions.
Here comes my little trick:
// In your beforeFilter() method of the AppController for example (after Auth adapter config!)
$allowed = array('Users' => array('login', 'lost_password', 'register'));
if (!$this->Session->check('Auth.User.id')) {
return;
}
foreach ($allowed as $controller => $actions) {
if ($this->name === $controller && in_array($this->request->action, $actions)) {
// Flash message - you can use your own method here - or CakePHP's setFlash() - as well
$this->Common->flashMessage('The page you tried to access is not relevant if you are already logged in. Redirected to main page.', 'info');
return $this->redirect($this->Auth->loginRedirect);
}
}
It is also visible in the linked CakeFest app demo.
UPDATE 2012-01-10
The auth model can now be anything you like. It doesn’t have to be Role
or role_id
.
The new CakePHP 2.x uses "groups" per default.
You can easily adjust that now by passing
or aclModel
=> 'Group'
to the Tiny class, for instance.aclKey
=> 'group_id'
UPDATE 2013-03-10
Some will be happy to hear that the deeper "contained" Role array is now supported besides the flat array of role keys. This deep/verbose array of roles has been introduced in CakePHP 2.2 with the new "contain" param for Auth. So it made sense to support this in TinyAuth. See the test case for details.
UPDATE 2013-06-25
A new option allowAdmin
makes it now possible to use TinyAuth even with less configuration in some cases. True
makes the admin role access any admin prefixed action and together with allowUser
(allows all logged in users to allow non admin prefixed URLs) this can be used to set up a basic admin auth. No additional configuration required except for the adminRole
config value which needs to be set to the corresponding integer value.
Of, course, you can additionally allow further actions. But if you just need to quickly enable an admin backend, this could be the way to go.
Notes
NOTE 2012-02-25
It seems that especially new-beys seem to mix up the meaning of *
in the ACL. Although it is already laid out in the above text I will try to make it more clear:
This any
placeholder for "roles" only refers to those users that are logged in. You must not declare your public actions this way!
All those must be declared in your controller using $this->Auth->allow()
(in earlier versions of CakePHP `$this->Auth->allow(‘*’)).
The reason is that Authenticate comes before Authorize. So without Authentication (logged in) there will never be any Authorization (check on roles).
NOTE 2013-02-12
You can use this in conjunction with my Auth class for a quick way to check on the current user and its role(s) anywhere in your application:
App::uses('Auth', 'Tools.Lib'); // In your bootstrap (after plugin is loaded)
if (Auth::id()) {
$username = Auth::user('username');
// do sth
}
if (Auth::hasRole(Configure::read('moderator'))) { // if you used configure slugs
// do sth
}
if (Auth::hasRoles(array(ROLE_ADMIN, ROLE_MODERATOR)) { // if you used configure and constants instead of magic numbers
// do sth
}
See the inline class documentation or the test cases for details.
Upcoming
A shell to quickly modify the INI file (and batch-update for new controllers etc) should be ready some time soon.
There might some day also the possibility to use some CRUD backend to manage the ACL (either via database or modifying the INI file).
If someone wants to help, go for it.
2014-09: CakePHP 3.0
With the release of CakePHP 3.0 I upgraded the TinyAuth code and moved it into an own repository.
Please see the wiki page there for docs and migration guide.