Cake 1.2 with Auth and ACL, from scratch

I came across quite a few great tutorials on how to use Cake's 1.2 ACL and Auth components, but they all seemed to be missing 1 or 2 key things. Something I realized when I got it all figured out, is that it's really not that complicated, it just takes a little getting used to.

The idea of this tutorial is to explain, step by step, how to setup a cake app using these components. My main goal is to create something that is really useful, and reusable in most apps, so if anyone has any corrections or ideas of how to simplify, I'm happy to hear them. I am by no means an expert.

There are several types of authentication you might want to do. Model-based, Controller-based, Action-based, and ACL. This will show you how to do ACL.

You can download an example aplication, to get started and follow along (without having to type things) here.

Bake a Cake

First we need a really simple app to test things, and so we are all on the same page. Let's start by baking an app. This is covered all over, so I won't go into it too much. I use linux, so you may have to do something different. Here is what I do to start a project:

cd ~/Projects/workspace/
/usr/local/lib/cake/cake-1.2/cake/console/cake -app blog bake

The first line is where I keep my Eclipse workspace. I have installed cake in the directory /usr/local/lib/cake/cake-1.2. the cake command will create a directory with the name of your app, and it should be a folder that doesn't exist. Now, I setup the database, with the same command:

/usr/local/lib/cake/cake-1.2/cake/console/cake -app blog bake

Answer the questions, make sure before you start it's a database that exists.

Setup ACL and Users

/usr/local/lib/cake/cake-1.2/cake/console/cake -app blog acl initdb

Now, we have an app all ready to add user data to. Auth has some default fields (username, password) that it uses automatically, but I want to authenticate with an email address, and I don't like using database reserved words (PASSWORD) for field/table names. It doesn't matter what the field names are, just make sure you have a "username" and "password" type of field, and make sure that the password field is large enough to store the hash that Auth creates of passwords. I used VARCHAR(50). Any other fields you add to this table will get loaded with the user and stored in your session, so you can add any info you want here (like name, address, whatever.) Here is the user table I create in mysql:

CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`email` VARCHAR( 50 ) NOT NULL ,
`passwd` VARCHAR( 50 ) NOT NULL ,
`created` DATETIME NULL ,
`modified` DATETIME NULL
) ENGINE = MYISAM ;

Now we use bake to setup the Model, Controller, and view:

/usr/local/lib/cake/cake-1.2/cake/console/cake -app blog bake

Hello konsumer,


Welcome to CakePHP v1.2.0.5427alpha Console
---------------------------------------------------------------
App : blog
Path: /home/konsumer/Projects/workspace/blog
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[M]odel
[V]iew
[C]ontroller
[Q]uit
What would you like to Bake? (M/V/C/Q)
> m
---------------------------------------------------------------
Model Bake:
---------------------------------------------------------------
Possible Models based on your current database:
1. User
Enter a number from the list above, or type in the name of another model.
> 1
Would you like to supply validation criteria for the fields in your model? (y/n)
[y] >

Name: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.


[5] >

Name: email
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.


[1] > 2

Name: passwd
Type: string
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.


[1] >

Name: created
Type: datetime
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.


[5] >

Name: modified
Type: datetime
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1- VALID_NOT_EMPTY
2- VALID_EMAIL
3- VALID_NUMBER
4- VALID_YEAR
5- Do not do any validation on this field.

... or enter in a valid regex validation string.


[5] >
Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n)
[y] > n

---------------------------------------------------------------
The following model will be created:
---------------------------------------------------------------
Model Name:        User
DB Connection: default
DB Table:       users
Validation:        Array
(
    [email] => VALID_EMAIL
    [passwd] => VALID_NOT_EMPTY
)

Associations:
---------------------------------------------------------------
Look okay? (y/n)
[y] > y

Creating file /home/konsumer/Projects/workspace/blog/models/user.php
Wrote /home/konsumer/Projects/workspace/blog/models/user.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
[y] > n
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[M]odel
[V]iew
[C]ontroller
[Q]uit
What would you like to Bake? (M/V/C/Q)
> c
---------------------------------------------------------------
Controller Bake:
---------------------------------------------------------------
Possible Models based on your current database:
1. Users
Enter a number from the list above, or type in the name of another controller.
> 1
Would you like bake to build your controller interactively?
Warning: Choosing no will overwrite Users controller if it exist. (y/n)
[y] > y
Would you like to use scaffolding? (y/n)
[y] > n
Would you like to include some basic class methods (index(), add(), view(), edit())? (y/n)
[n] > y
Would you like to create the methods for admin routing? (y/n)
[n] > y
Would you like this controller to use other models besides 'User'? (y/n)
[n] > n
Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n)
[n] >
Would you like this controller to use any components? (y/n)
[n] >
Would you like to use Sessions? (y/n)
[y] >
You need to enable CAKE_ADMIN in /app/config/core.php to use admin routing.
What would you like the admin route to be?
Example: www.example.com/admin/controller
What
would you like the admin route to be?
[admin] >

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:        Users
---------------------------------------------------------------
Look okay? (y/n)
[y] >

Creating file /home/konsumer/Projects/workspace/blog/controllers/users_controller.php
Wrote /home/konsumer/Projects/workspace/blog/controllers/users_controller.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
[y] > n
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[M]odel
[V]iew
[C]ontroller
[Q]uit
What would you like to Bake? (M/V/C/Q)
> v
---------------------------------------------------------------
View Bake:
---------------------------------------------------------------
Possible Models based on your current database:
1. Users
Enter a number from the list above, or type in the name of another controller.
> 1
Would you like bake to build your views interactively?
Warning: Choosing no will overwrite Users views if it exist. (y/n)
[y] >
Would you like to create some scaffolded views (index, add, view, edit) for this controller?
NOTE: Before doing so, you'll need to create your controller and model classes (including associated models). (y/n)
[n] > y
Would you like to create the views for admin routing? (y/n)
[y] > y

Creating file /home/konsumer/Projects/workspace/blog/views/users/index.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/index.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/view.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/view.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/add.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/add.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/edit.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/edit.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/admin_index.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/admin_index.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/admin_view.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/admin_view.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/admin_add.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/admin_add.ctp

Creating file /home/konsumer/Projects/workspace/blog/views/users/admin_edit.ctp
Wrote /home/konsumer/Projects/workspace/blog/views/users/admin_edit.ctp
---------------------------------------------------------------

View Scaffolding Complete.

Ok, so everything is all setup. You should be able to visit yoursite/admin/users/ and see a list of no users. I won't go into the details of setting up apache to point to your app's webroot, you should have this part down, or check out the cake manual. Don't add any users, until we setup Auth, as it will automatically hash passwords.

Setup Auth

Edit app_controller.php:

<?php
class AppController extends Controller {
    var
$components = array('Auth');
    var
$beforeFilter = array('setupAuth');

    function
setupAuth(){
       
$this->Auth->fields = array('username' => 'email', 'password' => 'passwd');
       
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
       
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
       
$this->Auth->logoutRedirect = '/';
       
$this->Auth->loginError = 'Invalid e-mail/password combination.  Please try again.';
       
$this->Auth->$actionPath='Controllers/';

        if (isset(
$this->allowedActions)){
           
$this->Auth->allowedActions = $this->allowedActions;
        }
    }

}
?>

Customize your views

Next, we setup the proper views for a login, register, and index (which I want to be "home", not a list of users)

cd blog/views/users/
mv add.ctp register.ctp
mv view.ctp index.ctp

Now, edit register.ctp, and remove the actions, at the bottom, and make it look good for your registration form. Here's what mine looks like:

<div class="user">
<?php echo $form->create('User');?>
<fieldset>
<legend><?php __('Register');?></legend>
<?php
       
echo $form->input('email');
        echo
$form->input('passwd');
   
?>

</fieldset>
<?php echo $form->end(__('Register',true));?>
</div>

I will also setup the index page:

<div class="user">
<h2><?php __('Home');?></h2>
<dl>
<dt><?php __('Email');?></dt>
<dd>
<?php echo $user['User']['email']?>
&nbsp;
</dd>
<dt><?php __('Created');?></dt>
<dd>
<?php echo $user['User']['created']?>
&nbsp;
</dd>
<dt class="altrow"><?php __('Modified');?></dt>
<dd class="altrow">
<?php echo $user['User']['modified']?>
&nbsp;
</dd>
</dl>
</div>

This isn't really neccessary, it will just give us something to test with (a restricted page, only for authenticated users.) Lastly, I change the edit form to not have any id stuff (or actions at the bottom).

<div class="user">
<?php echo $form->create('User');?>
<fieldset>
<legend><?php __('Edit');?> <?php __('Account');?></legend>
<?php
       
echo $form->input('email');
        echo
$form->input('passwd');
   
?>

</fieldset>
<?php echo $form->end(__('Save',true));?>
</div>

Also, add a login view. I just copied "edit" to "login" and made it look right:

<div class="user">
<?php echo $form->create('User');?>
<fieldset>
<legend><?php __('Login');?></legend>
<?php
       
echo $form->input('email');
        echo
$form->input('passwd');
   
?>

</fieldset>
<?php echo $form->end( __('Login', true));?>
<small>If you have not registered, you may <?php echo $html->link('create an account', '/users/register');?>.</small>
</div>

Setup Users controller

Edit controllers/users_controller.php. First, I remove the index function, and rename view to index. I set it up to use the authticated user, instead of $id. I also rename add to register. And remove "delete". I leave "edit" so users can edit their account, but I converted it to use the authenticated user. I will also add an allowed action to $publicActions, so that non-authenticated users can register. I also add a login and logout action here. These should not be in $publicActions, or things will get all screwy.

<?php
class UsersController extends AppController {

    var
$name = 'Users';
    var
$helpers = array('Html', 'Form' );
    var
$publicActions=array('register');

    function
login() {
    }

    function
logout() {
       
$this->Session->setFlash("You've successfully logged out.");
       
$this->redirect($this->Auth->logout());
    }

    function
index() {
       
$this->set('user', $this->User->read(null, $this->Auth->user('id')));
    }

    function
register() {
        if (!empty(
$this->data)) {
           
$this->cleanUpFields();
           
$this->User->create();
            if (
$this->User->save($this->data)) {
               
$this->Session->setFlash('Your account has been saved');
               
$this->redirect(array('action'=>'index'), null, true);
            } else {
               
$this->Session->setFlash('Your account could not be saved. Please, try again.');
            }
        }
    }

    function
edit() {
        if (!empty(
$this->data)) {
           
$this->cleanUpFields();
           
$this->data['User']['id']=$this->Auth->user('id');
            if (
$this->User->save($this->data)) {
               
$this->Session->setFlash('Your account has been saved.');
               
$this->redirect(array('action'=>'index'), null, true);
            } else {
               
$this->Session->setFlash('Your account could not be saved. Please, try again.');
            }
        }
        if (empty(
$this->data)) {
           
$this->data = $this->User->read(null, $this->Auth->user('id'));
        }
    }

    function
admin_index() {
       
$this->User->recursive = 0;
       
$this->set('users', $this->paginate());
    }

    function
admin_view($id = null) {
        if (!
$id) {
           
$this->Session->setFlash('Invalid User.');
           
$this->redirect(array('action'=>'index'), null, true);
        }
       
$this->set('user', $this->User->read(null, $id));
    }

    function
admin_add() {
        if (!empty(
$this->data)) {
           
$this->cleanUpFields();
           
$this->User->create();
            if (
$this->User->save($this->data)) {
               
$this->Session->setFlash('The User has been saved');
               
$this->redirect(array('action'=>'index'), null, true);
            } else {
               
$this->Session->setFlash('The User could not be saved. Please, try again.');
            }
        }
    }

    function
admin_edit($id = null) {
        if (!
$id && empty($this->data)) {
           
$this->Session->setFlash('Invalid User');
           
$this->redirect(array('action'=>'index'), null, true);
        }
        if (!empty(
$this->data)) {
           
$this->cleanUpFields();
            if (
$this->User->save($this->data)) {
               
$this->Session->setFlash('The User saved');
               
$this->redirect(array('action'=>'index'), null, true);
            } else {
               
$this->Session->setFlash('The User could not be saved. Please, try again.');
            }
        }
        if (empty(
$this->data)) {
           
$this->data = $this->User->read(null, $id);
        }
    }

    function
admin_delete($id = null) {
        if (!
$id) {
           
$this->Session->setFlash('Invalid id for User');
           
$this->redirect(array('action'=>'index'), null, true);
        }
        if (
$this->User->del($id)) {
           
$this->Session->setFlash('User #'.$id.' deleted');
           
$this->redirect(array('action'=>'index'), null, true);
        }
    }

}
?>

At this point we have all the basics setup.

Who's online

There are currently 1 user and 0 guests online.

Online users

  • mbavio