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.
Hi there,
I have been trying to get this going for a few hours now. I wonder if I am missing something obvious.
Authentication is working fine without your plugin (with no roles). I am using a role model with containable. My array looks like this:
[code type=php}
array(
‘User’ => array(
‘id’ => ‘1’,
‘username’ => ‘mark’,
‘Role’ => array(
(int) 0 => array(
‘id’ => ‘1’,
‘alias’ => ‘Superadmin’,
‘RolesUser’ => array(
‘id’ => ‘1’,
‘role_id’ => ‘1’,
‘user_id’ => ‘1’
)
),
(int) 1 => array(
‘id’ => ‘2’,
‘alias’ => ‘Owner’,
‘RolesUser’ => array(
‘id’ => ‘2’,
‘role_id’ => ‘2’,
‘user_id’ => ‘1’
)
)
)
)
)
{/code}
I’ve done a bit of debugging here and there and can confirm the values are being loaded from the acl.ini file. But as soon as I enable the plugin using
I get stuck in a redirect loop.
My component array looks like this:
Any tips?
Many thanks in advance, Mark
Here is that array again with proper formatting:
How does your acl.ini look like?
Did you try to debug the TinyAuth class where exactly it breaks?
Also make sure you are using the current master (head).
Hi Mark,
Thanks for such a quick response!
I have tried fiddling with the ini quite a bit, it currently looks like this:
I downloaded your whole Tools plugin from github just today, so presumably it is the most recent version.
I have been debugging the TinyAuthorize.php file, trying to figure out where it gets to. I got as far as debugging the $iniArray var (which contained my ini statements), and $availableRoles which contains all the roles in my database.
I will try some more debugging tomorrow.
Many thanks again,
Mark
You seem to have a multi-role setup (instead of a single belongsTo relation, did you configure Tiny appropriately?
Note that there is a test case that seems to proof that your exact array setup in the session should work just fine.
Maybe you can write test case that disproves that?
Thanks for your work! A noob question: I am looking for a solution where I don’t authorize users for a whole action but e.g. I wan’t users in all index-actions only to see those model-instances that are bound to their userid. Does TinyAuth the trick or would it be better to use some magic from within the cakephp framework?
I would probably do that as inline checks, usually this is often just a condition to append to the find() call you are doing anyway.
Thanks for the fast reply!! I will try that. 🙂
Hi! I’ve got a relation HABTM (One User – Many Roles), but this plugin doesn’t work! 🙁
When I debug User Session data, it looks like
and I configured the AppController like this
I’ve got a ‘users’ table and a ‘roles’ table, and a ‘roles_users’ join table with a hasAndBelongsToMany relationship that works (join table is populated successfully).
What am I doing wrong? 🙁
Well, the session data looks right. And the config too,
see the test case ( https://github.com/dereuromark/cakephp-tools/blob/master/Test/Case/Controller/Component/Auth/TinyAuthorizeTest.php#L179 ) which covers exactly that.
What exactly doesnt work?
Is the access not granted? Maybe there is a different error here.
Try to debug it where exactly it fails.
Hi @ all,
i’ve got the problem that in the user sessions array the roles are not shown.
I checked all of theses posts but nothing helps.
About the Application: I run a HABTM user / roles setup. In my user and roles model there is the HABTM statement as follows:
My AppController is doing this
my acl.ini is pretty easy atm
the TinyAuthorize Plugin file
i can’t find the error.
Thanks for you help.
Timo
Hi,
I would like to know that how can i stop or unauthorized multiple logins with the same username/ password.
Here is a part of my acl.ini
if I know the user’s role is "admin", is there a way (preferably from the template page), that I can determine whether or not that user can have access to let’s say the cities/admin_ajax_edit page?
Yes, with the latest version it now is possible 🙂 See the documentation to the new AuthUser component and helper.