Auth and ACL an end to end tutorial pt. 1
Now, there are many tutorials out there for Auth and ACL in CakePHP. However, none of them (as far as I know of) cover putting together Auth and ACL from beginning to end. That is what I’m going to do in this article. I am splitting the article into two parts; the first about setting up our app and getting the Aros running, the second on building Acos and putting it all together.
With that said, this article assumes you know the basics of CakePHP, and are familiar with all of the MVC core concepts. You are also comfortable using bake
and the cake console
. If not, learn those concepts first, and then come back. After learning these concepts everything else will make more sense. This tutorial will also use Auth in actions
mode, this authenticates our ARO – groups and users – against the ACO objects – controllers & actions.
Tabula rasa
I am going to start with a blank install of cake that has just been checked out / unpacked, and has had database.php
config file setup and the Security.salt
value changed to remove the warning. If you have an app in progress that should work as well, but you will have to make adjustments for the user, and group models accordingly.
Schema-ing
First things first, we need to make some tables to base our application off of. We are going to create tables for users, groups, posts and widgets. This will give us a few things we can try our ACL system against.
- CREATE TABLE users (
- id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
- username VARCHAR(255) NOT NULL UNIQUE,
- password CHAR(40) NOT NULL,
- group_id INT(11) NOT NULL,
- created DATETIME,
- modified DATETIME
- );
- CREATE TABLE groups (
- id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- created DATETIME,
- modified DATETIME
- );
- CREATE TABLE posts (
- id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
- user_id INT(11) NOT NULL,
- title VARCHAR(255) NOT NULL,
- body TEXT,
- created DATETIME,
- modified DATETIME
- );
- CREATE TABLE widgets (
- id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- part_no VARCHAR(12),
- quantity INT(11)
- );
These are the tables I used to build the rest of this article, so get them into your database if you are playing at home. Once we have the table structure in the db we can start cooking. Bake up the models, controllers, and views. Leave out admin routing for now, this is a complicated enough subject without them. Also be sure not to add either the Acl or Auth Components to any of your controllers, we’ll be doing that soon enough. You should now have models, controller, and baked views for your users, groups, posts and widgets.
Preparing the application for Auth and Acl
We now have a functioning CRUD application. Bake should have setup all the relations we need, if not do that now. There are a few other pieces that need to be added before we can add components. First add a login and logout action to your UsersController
.
- function login() {
- //Auth Magic
- }
- function logout() {
- //Auth Magic
- }
We don’t need to worry about adding anything to hash passwords, as Auth
will do this for us automatically when creating/editing users, and when they login. Furthermore, if you hash the passwords manually Auth
will not be able to log you in at all.
AppController
Next we need to make some modifications to AppController
. If you haven’t made one in app/
do so now. Since we want our entire site controlled with Auth and Acl, we will set them up in our AppController
. Add the following to AppController
:
- function beforeFilter() {
- //Configure AuthComponent
- $this->Auth->authorize = 'actions';
- }
We also need to add a few temporary overrides to the groups and users controllers. Add the following to both the users and groups controllers.
- function beforeFilter() {
- parent::beforeFilter();
- }
These overrides shut off the Auth component so we can actually get some data in our tables. If we left this out, Auth would do its job and lock us out. Not very useful.
Initialize the Acl
Before we can start using the Acl we need to initialize it. If you try to view any pages right now, you will get a missing table error, so lets get rid of that. In a shell run the following cake schema run create DbAcl
and follow the prompts to drop and create the tables.
With the controllers setup for data entry, and the Acl tables initialized we are ready to go right? Not entirely, we still have a bit of work to do in the user and group models. Namely, making them auto-magically attach to the Acl.
Acts As a Requester
Auth stores information about the currently logged in user in the Session, however we are building an Access Control System based on groups. Therefore, we need to associate our users to our groups in the Acl. In order to do this we can use the AclBehavior. The AclBehavior allows for the automagic connection of models with the Acl tables. Its use requires an implementation of parentNode()
on your model. In our User
model we will add the following.
- function parentNode() {
- return null;
- }
- $data = $this->data;
- $data = $this->read();
- }
- if (!$data['User']['group_id']) {
- return null;
- } else {
- }
- }
Then in our Group
Model Add the following:
What this does, is tie the Group
and User
models to the Acl, and tell CakePHP that every-time you make a User or Group you want an entry on the aros
table as well. This makes Acl management a piece of cake as your AROs become transparently tied to your users
and groups
tables.
So now our controllers are prepped for adding some initial data, and our Group
and User
models are bound to the Acl table. So add some groups and users. I made the following groups:
- administrators
- managers
- users
I also created a user in each group so I had some different users to test with later. Write everything down or use easy passwords so you don’t forget. If you do a SELECT * FROM aros;
from a mysql prompt you should get something like the following:
- +----+-----------+-------+-------------+-------+------+------+
- | id | parent_id | model | foreign_key | alias | lft | rght |
- +----+-----------+-------+-------------+-------+------+------+
- | 1 | NULL | Group | 1 | NULL | 1 | 4 |
- | 2 | NULL | Group | 2 | NULL | 5 | 8 |
- | 3 | NULL | Group | 3 | NULL | 9 | 12 |
- | 4 | 1 | User | 1 | NULL | 2 | 3 |
- | 5 | 2 | User | 2 | NULL | 6 | 7 |
- | 6 | 3 | User | 3 | NULL | 10 | 11 |
- +----+-----------+-------+-------------+-------+------+------+
- 6 rows in set (0.00 sec)
This shows us that we have 3 groups and 3 users. The users are nested inside the groups, which opens up the possibility of setting per-user permissions.
One thing I noticed when making this tutorial, is that if you modify a User’s group it does not modify their Aro. You have to do that separately it seems.
So that concludes the first part of this series. We now have a users/groups system bound for the most part to the Acl (see above). Up next getting our controllers and actions into the acos
table and getting everything to jive together. Also, at the end of this process I will be making all the files I used to build this tutorial with all the SQL dumps available for your perusal.
Update: Part two is now up
Update: Corrected Acl initialization shell command. Thanks Jason. Also simplified Users::parentNode() function.
Another good looking and informative post Mark. I’ve recently stepped into CakePHPs ACL / Auth components and really look forward to the next parts of this tutorial.
Definitely an improvement over other tutorials that only show the how and don’t explain the why :)
anonymous user on 7/7/08
Great first article in the series, ACL is a tough subject to grasp and explain thoroughly. So far you’ve done good.
anonymous user on 7/7/08
Very nice tutorial, but I have a question: What’s the difference or advantage on using an extra table for groups and not defining the groups as aro’s or aco’s, for exampe as stated in the official cakephp book?
anonymous user on 7/8/08
@Juan Luis Baptiste: The advantage comes from being able to create drop downs when creating users, and other interface elements. Furthermore, it makes it easier for others to understand the user hierarchy of the site, instead of having to guess at ARO associations.
mark story on 7/8/08
This might be a good candidate for the Common Tasks with CakePHP at book.cakephp.org. I know there are detailed explanations of ACL and Authentication in the Components section, but a more tutorial-style approach would be good as well.
anonymous user on 7/9/08
This might be a good candidate for the Common Tasks with CakePHP at book.cakephp.org. I know there are detailed explanations of ACL and Authentication in the Components section, but a more tutorial-style approach would be good as well.
anonymous user on 7/9/08
There’s a slight typo in your code:
var $actsAs = array('requester');
in the
User
model should read:var $actsAs = array('Acl' => array('requester'));
Good stuff Mark. Keep em’ coming.
anonymous user on 7/9/08
@Joel: thanks for the find, its been fixed it up.
@Brian: I have every intention of posting it to either the book.cakephp.org or bakery.cakephp.org once the tutorial done.
mark story on 7/10/08
@Mark: thank you for your answer. Another question, when do you think you are going to publish the second part of the article ?
anonymous user on 7/11/08
Great stuff, thanks! It’s coming up with “Fatal error: Call to undefined method stdClass::node() in C:\wamp\www\cake\app\models\user.php on line 19” every time I add a user. That’s this line of the user model “$groupNode = $this->Group->node();”. Any ideas? Thanks!
anonymous user on 7/16/08
Never mind! Just needed to sort my hasMany’s and belongsTo’s out.
anonymous user on 7/16/08
Mark, great article; thanks for putting it together. As I was following along I noticed that the shell command
cake acl initdb
is deprecated for 1.2. The Cookbook has the new command.anonymous user on 8/1/08
Hi Mark
Great tutorial it’s really got me to understand how I should use ACL.
The reason why the ARO isn’t updated when modifying a user is in the afterSave method of the AclBehavior.
Nothing happens when you save.
anonymous user on 9/4/08
Well, with some time of from the problem. I think I’ve come up with a solution.
anonymous user on 9/6/08
When I get to the bake part, telling me to create the models, views, and controllers for the 4 things, I understand that. But bake asks all kinds of questions, like if I want scaffolding. What should I put? Also, am I doing it right if I have to do “m” 4 times, “v” 4 times, and “c” 4 times? Just doesn’t seem too automagic when so much input is required, so I thought I may be doing it wrong.
anonymous user on 10/15/08
beatles: There is a
cake bake all
function as well, but I don’t think that bake is really that much work considering how much typing time it saves. You can use scaffolds but I prefer to not use them.mark story on 10/18/08
Thanks for the tutorial! it helps a lot…
jpablobr on 5/30/09
Warning (512): DbAcl::check() – Failed ARO/ACO node lookup in permissions check. Node references:
Aro: Array
( [User] => Array ( [id] => 1 [username] => admin [group_id] => 1 )
)
Aco: Pages/display [CORE\cake\libs\controller\components\acl.php, line 239]
Code | Context
$aro = array( “User” => array( “id” => “1”, “username” => “admin”, “group_id” => “1”
if (empty($aroPath) || empty($acoPath)) { trigger_error(“DbAcl::check() – Failed ARO/ACO node lookup in permissions check. Node references:\nAro: “ . print_r($aro, true) . “\nAco: “ . print_r($aco, true), E_USER_WARNING);)
)
$aco = “Pages/display”
$action = “*”
$permKeys = array( “_create”, “_read”, “_update”, “_delete”
)
$aroPath = array( array( “Aro” => array()
), array( “Aro” => array()
)
)
$acoPath = false
DbAcl::check() – CORE\cake\libs\controller\components\acl.php, line 239
AclComponent::check() – CORE\cake\libs\controller\components\acl.php, line 89
AuthComponent::isAuthorized() – CORE\cake\libs\controller\components\auth.php, line 477
AuthComponent::startup() – CORE\cake\libs\controller\components\auth.php, line 399
Component::startup() – CORE\cake\libs\controller\component.php, line 112
Dispatcher::_invoke() – CORE\cake\dispatcher.php, line 210
Dispatcher::dispatch() – CORE\cake\dispatcher.php, line 194
[main] – APP\webroot\index.php, line 88
Warning (2): Cannot modify header information – headers already sent by (output started at C:\Programme\xampp\htdocs\cake\cake\basics.php:111) [CORE\cake\libs\controller\controller.php, line 640]
Code | Context
$status = “Location: http://localhost/”
header – [internal], line ??
Controller::header() – CORE\cake\libs\controller\controller.php, line 640
Controller::redirect() – CORE\cake\libs\controller\controller.php, line 621
AuthComponent::startup() – CORE\cake\libs\controller\components\auth.php, line 404
Component::startup() – CORE\cake\libs\controller\component.php, line 112
Dispatcher::_invoke() – CORE\cake\dispatcher.php, line 210
Dispatcher::dispatch() – CORE\cake\dispatcher.php, line 194
[main] – APP\webroot\index.php, line 88
Madi on 6/13/09
OOOps! Sorry it didn’t copy all the message! Anyway, that is my problem! Thanks for your help!
Madi on 6/13/09
on 6/30/09