CakePHP 1.2.* Auth Component Tutorial

December 20th, 2008 | Tags: ,

There are a lot of tutorials out there on how to use the Auth component in CakePHP but everything that I’ve found so far has been lacking for my need – so I wrote my own. Recently I needed a way to set up a simple authentication mechanism I could use for customer websites in order for them to log in and manage their content. Since I use CakePHP for everything these days one of the simplest solutions was to utilize the built in authentication component which can be a little tricky to set up since the documentation is a little vague and scattered over the Internet.

All of this code is provided in a zip file at the bottom of this post. Also, feel free to leave any feedback if you have comments or suggestions.

Database

The first thing you will want to do is create a user table in your site database:

/app/config/sql/users.sql

CREATE TABLE `users` (
    `id` int(11) unsigned NOT NULL auto_increment,
    `username` varchar(20) NOT NULL default '',
    `password` varchar(40) NOT NULL default '',
    `name` varchar(40) NOT NULL default '',
    `email` varchar(60) NOT NULL default '',
    `created` datetime default NULL,
    `updated` datetime default NULL,
    PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

Your table doesn’t have to be exactly like this so feel free to adjust it to your own needs. However later on I will include CRUD pages for the users and you may need to adjust them to suit your changes.

User Model

Now that you have your table set up lets create the user model.

/app/models/user.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
class User extends AppModel {
    var $name = 'User';
    var $useTable = 'users';
    var $validate = array(
        'username' => array(
            'empty' => array(
                'rule' => 'notEmpty',
                'required' => true,
                'allowEmpty' => false,
                'message' => 'Please enter a username'
            ),
            'length' => array(
                'rule' => array('minLength', 4),
                'required' => true,
                'allowEmpty' => true,
                'message' => 'Usernames must be at lest 4 characters long'
            ),
            'alphanum' => array(
                'rule' => 'alphaNumeric',
                'required' => true,
                'allowEmpty' => true,
                'message' => 'Only letters and numbers are allowed in usernames'
            ),
            'unique' => array(
                'rule' => 'isUnique',
                'required' => true,
                'allowEmpty' => true,
                'message' => 'That username is already in use by another user'
            )
        ),
        'clear_password' => array(
            'empty' => array(
                'rule' => 'notEmpty',
                'required' => true,
                'allowEmpty' => false,
                'on' => 'create',
                'message' => 'Please enter a password'
            ),
            'length' => array(
                'rule' => array('minLength', 6),
                'required' => true,
                'allowEmpty' => true,
                'message' => 'Passwords must be at lease 6 characters long'
            )
        ),
        'confirm_password' => array(
            'empty' => array(
                'rule' => 'notEmpty',
                'required' => true,
                'allowEmpty' => false,
                'on' => 'create',
                'message' => 'Please confirm the password'
            ),
            'emptyUpdate' => array(
                'rule' => 'emptyUpdate',
                'required' => true,
                'on' => 'update',
                'message' => 'Please confirm the password'
            ),
            'match' => array(
                'rule' => 'matchPasswords',
                'required' => true,
                'allowEmpty' => true,
                'message' => 'The passwords you entered do not match'
            )
        ),
        'name' => array(
            'rule' => 'notEmpty',
            'required' => true,
            'allowEmpty' => false,
            'message' => 'Please enter a name'
        ),
        'email' => array(
            'empty' => array(
                'rule' => 'notEmpty',
                'required' => true,
                'allowEmpty' => false,
                'message' => 'Please enter an email address'
            ),
            'email' => array(
                'rule' => 'email',
                'required' => true,
                'allowEmpty' => true,
                'message' => 'Please enter a valid email address'
            ),
            'unique' => array(
                'rule' => 'isUnique',
                'required' => true,
                'allowEmpty' => true,
                'message' => 'That email address is already in use by another user'
            )
        )
    );

    function emptyUpdate() {
        if (!empty($this->data['User']['clear_password']) &amp;&amp; empty($this->data['User']['confirm_password'])) {
            return false;
        } else {
            return true;
        }
    }

    function matchPasswords() {
        if ($this->data['User']['clear_password'] != $this->data['User']['confirm_password']) {
            return false;
        } else {
            return true;
        }
    }
}
?>

I went ahead and included all the validation that I use as well so again feel free to adjust as necessary.

Users Controller

And then for the Users Controller…

/app/controllers/users_controller.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php
class UsersController extends AppController {
    var $name = 'Users';
    var $uses = array('User');

    function beforeFilter() {
        parent::beforeFilter();
    }

    function index() {
        $users = $this->User->find('all');
        if (empty($users)) {
            $this->Session->setFlash('There are no users defined', 'default', array('class'=>'bad'));
        } else {
            $this->set('users', $users);
        }
    }

    function view($id) {
        $user = $this->User->findById($id);
        if (!empty($user)) {
            $this->set('user', $user);
        } else {
            $this->Session->setFlash('Invalid User ID', 'default', array('class'=>'bad'));
            $this->redirect('index');
        }
    }

    function add() {
        if (!empty($this->data)) {
            $this->User->set($this->data);
            if ($this->User->validates()) {
                $this->data['User']['password'] = $this->data['User']['clear_password'];
                $this->User->save($this->Auth->hashPasswords($this->data), false);
                $this->Session->setFlash($this->data['User']['name'].' Added', 'default', array('class'=>'good'));
                $this->redirect('index');
            } else {
                $this->Session->setFlash('Please correct the errors below', 'default', array('class'=>'bad'));
            }
        }
    }

    function edit($id = null) {
        if (!empty($this->data)) {
            foreach ($this->data['User'] as $field => $data) {
                if (!in_array($field, array('clear_password', 'confirm_password'))) {
                    $fields[] = $field;
                }
            }
            if (!empty($this->data['User']['clear_password']) || !empty($this->data['User']['confirm_password'])) {
                $fields[] = 'password';
                $fields[] = 'clear_password';
                $fields[] = 'confirm_password';
            }
            $this->User->set($this->data);
            if ($this->User->validates()) {
                if (!empty($this->data['User']['clear_password'])) {
                    $this->data['User']['password'] = $this->data['User']['confirm_password'];
                }
                $this->User->save($this->Auth->hashPasswords($this->data), false, $fields);
                $this->Session->setFlash($this->data['User']['name'].' Updated', 'default', array('class'=>'good'));
                $this->redirect('index');
            }
        } else {
            $user = $this->User->findById($id);
            if (empty($user)) {
                $this->Session->setFlash('Invalid User ID', 'default', array('class'=>'bad'));
                $this->redirect('index');
            } else {
                unset($user['User']['password']);
                $this->data = $user;
            }
        }
    }

    function delete($id) {
        $user = $this->User->findById($id);
        if (empty($user)) {
            $this->Session->setFlash('Invalid User ID', 'default', array('class'=>'bad'));
        } else {
            if ($user['User']['id'] == $this->Session->read('Auth.User.id')) {
                $this->Session->setFlash('Sorry, you can not delete yourself', 'default', array('class'=>'bad'));
            } else {
                if ($this->User->del($id)) {
                    $this->Session->setFlash($user['User']['name'].' Deleted', 'default', array('class'=>'good'));
                } else {
                    $this->Session->setFlash('Failed to delete '.$user['User']['name'], 'default', array('class'=>'bad'));
                }
            }
        }
        $this->redirect('index');
    }

    function login() {
        // Auth Magic
    }

    function logout() {
        $this->Session->del('Auth.User');
        $this->Session->setFlash('Your are now logged out', 'default', array('class'=>'bad'));
        $this->redirect('login');
    }
}
?>

Before Filter

I have found that it is a good habit to always inherit your app controller’s before filter. You will not want to forget this if you customize any of the Auth component variables. Otherwise they will not be set as you expect when you declare a beforeFilter method in any other controller. You may have noticed the array of methods I passed to Auth component. You will need to uncomment that line in order to add users after enabling the Auth component in your app controller or you will be locked out of your site. You will also want to add similar before filters in all your other controllers to control access accordingly. Please see the documentation for the allow method for more information.

Password Hash Issue

One of the things I find irritating about the Auth component is how it uses the hash in the config file to encrypt the password by default instead of using something easy like md5. I’m sure they have their reasons and supposedly you can change it by setting Security::setHash(‘md5′); somewhere but I wasn’t able to get that working right. So I just figured out how to work with it as is.

If you take a look at the add function in the users controller you will see how I managed it. The first problem was that Auth encrypts the password field immediately. So if you had any errors in the registration the password that is placed back in the form field is encrypted and will no longer match the confirm password field. I changed the name of the password field to clear_password and then did my validation against that. After it passes validation the password is set and encrypted in the save.

Only change the password if one is entered problem

Now if you look at the edit function you will notice another problem. Usually in order to avoid creating a separate change password function I like to set up the password fields in the edit user page. If the password and confirm password are not entered then the password will not be changed. This creates a problem with cake because it likes to validate fields for us and the Auth component will even take a blank value and produce a nice 40 character hash string. So the craziness that is going on there is just a way around that issue using the additional fields attribute for the save method.

Other Controllers

Now don’t forget to open up your other controllers and add a before method with an exception for your public pages:

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

You could also allow(‘*’) in your app_controller and then specify which pages to deny, whichever way is easiest for you.

If you have any plugins you will also need to add before filters to the plugin app controller and all of the plugin controllers. If all the methods in a controller require authentication you can feel free to leave out the Auth->allow. Also never add an exception for login and logout. I have seen several people doing this and it is not necessary and could possibly break the auth component.

User Views

There is not a whole lot to explain here. It is just a the views for the users controller. Feel free to use them or ignore them.

/app/views/users/add.ctp

1
2
3
4
5
6
7
8
9
10
11
<h2>Add User</h2>
<?php
echo $form->create('User', array('url' => '/users/add'));
echo $form->input('username', array('size' => '20'));
echo $form->input('clear_password', array('type' => 'password', 'size'=>'20'));
echo $form->input('confirm_password', array('type'=> 'password', 'size' => '20'));
echo $form->input('name', array('size' => '35'));
echo $form->input('email',array('label' => 'Email Address', 'size' => '40'));
echo '<div><input type="submit" value="Submit" /> or '.$html->link('Cancel', array('action' => 'index')).'</div>';
echo $form->end();
?>

/app/views/users/edit.ctp

1
2
3
4
5
6
7
8
9
10
11
12
13
<h2>Edit User</h2>
<?php
echo $form->create('User', array('url' => '/users/edit'));
echo $form->input('username', array('size' => '20'));
echo 'Leave the password fields blank if you do not wish to change this users password';
echo $form->input('clear_password', array('type' => 'password', 'size'=>'20'));
echo $form->input('confirm_password', array('type'=>'password', 'size' => '20'));
echo $form->input('name', array('size' => '35'));
echo $form->input('email',array('label' => 'Email Address', 'size' => '40'));
echo $form->hidden('id');
echo '<div><input type="submit" value="Submit" /> or '.$html->link('Cancel', array('action' => 'index')).'</div>';
echo $form->end();
?>

/app/views/users/index.ctp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<h2>Users</h2>
<div>
<?php
echo $html->link('Add User', array('action' => 'add'));
?>
</div>
<hr />
<table width="100%">
    <tbody>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th></th>
        </tr>
<?php
if (!empty($users)) {
    foreach ($users as $i) {
        echo '<tr>';
        echo '  <td>'.$i['User']['id'].'</td>';
        echo '  <td>'.$i['User']['name'].'</td>';
        echo '  <td><a href="mailto: '.$i['User']['email'].'">'.$i['User']['email'].'</a></td>';
        echo '  <td class="actions">';
        echo $html->link('View', array('action' => 'view', $i['User']['id'])).' | ';
        echo $html->link('Edit', array('action' => 'edit', $i['User']['id'])).' | ';
        echo $html->link('Delete', array('action' => 'delete', $i['User']['id']), null, 'Are you sure you want to delete this user?');
        echo '  </td>';
        echo '</tr>';
    }
}
?>
    </tbody>
</table>

/app/views/users/login.ctp

1
2
3
4
5
6
7
<h2>Login</h2>
<?php
echo $form->create('User', array('url' => '/users/login'));
echo $form->input('username', array('size' => '20'));
echo $form->input('password', array('size' => '20'));
echo $form->end('Login');
?>

/app/views/users/view.ctp

1
2
3
4
5
6
7
8
9
10
11
<h2><?php echo $user['User']['name']; ?></h2>
<div>
<?php
echo $html->link('Back to Users', array('action' => 'index')).' | ';
echo $html->link('Edit', array('action' => 'edit', $user['User']['id'])).' | ';
echo $html->link('Delete', array('action' => 'delete', $user['User']['id']), null, 'Are you sure you want to delete this user?');
?>
</div>
<hr />
<p>Username: <?php echo $user['User']['username']; ?></p>
<p>Email Address: <?php echo '<a href="mailto: '.$user['User']['email'].'">'.$user['User']['email'].'</a>'; ?>

App Controller

Now that everything is set up let’s enable the auth component in the app controller (If you do not have one you may need to create it).

/app/app_controller.php

1
2
3
4
5
6
7
8
9
10
11
<?php
class AppController extends Controller {
    var $components = array('Auth');

    function beforeFilter() {
        $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
        $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
        $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
    }
}
?>

Login Action

The login action used here is actually set by default. You will need to change this if you have a different name for you users controller or login action.

Logout Redirect

The logout redirect is where users will be redirected after logging out. This is also set to users->login by default.

Login Redirect

Login redirect is actually a fall back option. Do not expect this to always redirect your users to the page specified by this option after logging in. The auth component stores the last visited page in the session and will redirect a successful login there unless autoRedirect is set to false in which case you will need to write a custom login function and handle redirection there. Or if a user has no previous session information (for example if they linked directly to the login page) in which case the auth component will use the login redirect method. This behavior is not very well documented on the Cake website. I actually found the answer in a closed bug report.

Because of the default options though you should be able to leave that stuff out and still be okay.

All Done!

Now you may visit /users/add and create a user account so you can log in and manage your site. DO NOT forget to remove the allow in your users controller for all the different pages you do not want to be public.

You can download all the code here.

  1. January 1st, 2009 at 05:43
    Reply | Quote | #1

    Thanks for the very well done tutorial. I have been hacking through a dozen different CakePHP auth tutorials and yours was the best I found. I did have to make some tweaks since my usage is a lot different. Great job though.

  2. January 1st, 2009 at 12:06
    Reply | Quote | #2

    Thanks Micah, I’m glad it helped you out :)
    Happy New Year!

  3. January 3rd, 2009 at 00:25
    Reply | Quote | #3

    Joseph, first I must say this is the most clear and defined explanation of user authentication and validation for Cake that I’ve seen so far. I’m not sure what is happening with my particular problem, or even that anyone can help; I copied your code nearly verbatim and still cannot even get the simplest validation rule to process. I’ve upgraded to the final release and I’m sure that it is something completely simple that I’m overlooking though after hours of frustration I thought it would be a good idea to throw it out there. I hope your year is bliss!

  4. January 3rd, 2009 at 08:40
    Reply | Quote | #4

    Thanks rcharm! I’m sorry to hear your having trouble. If you want you could email me the code from your model and I will see if I could be of any help. jbcrawford at gmail dot com. Hope you have a great new year as well :)

  5. andrew
    January 14th, 2009 at 23:57
    Reply | Quote | #5

    Joseph, thanks for the tip about inheriting the app controller’s beforeFilter(). I couldn’t figure out why Auth -> loginRedirect wasn’t doing its job, but I think you and Google saved the day. :)

  6. vijay
    January 23rd, 2009 at 06:49
    Reply | Quote | #6

    hi,
    I saw ur article its too nice.I am having the trouble when i use the user name as a session.how can i receive the user name value in the index page.

  7. January 23rd, 2009 at 12:26
    Reply | Quote | #7

    @vijay
    Hey vijay,
    Thanks for the comment!
    If you read the session Auth variable into your view you should be able to do it with something like this…

    $info = $session-&gt;read('Auth');
    echo $info['User']['username'];

    Hope that helps :)

  8. vijay
    January 24th, 2009 at 00:11
    Reply | Quote | #8

    Hi Joseph,
    Thanks for your reply man.Its working yar.Really its nice coding.Shall you tell me how to edit only user’s log in name and password.

  9. vijay
    January 24th, 2009 at 00:53
    Reply | Quote | #9

    Hey Josep,
    read(‘Auth’);
    $id= $info['User']['id'];
    ?>
    I used this code to receive the id value for logged in person and now i want to edit that person user name and password.how can i put this value in below mension edit link.
    link(‘EditProfile’, array(‘action’ => ‘edit/$id’));
    ?>
    Presently i am using this way.
    <a href=”edit/”>EditProfile
    I know it is not a good method in php.Is there any good coding for editing the profile?
    Thanks

  10. January 24th, 2009 at 09:52

    @vijay
    Using the html helper you could create a link to the edit user page like this:

    echo $html->link('Edit Profile', array('admin' => false, 'plugin' => null, 'controller' => 'users', 'action' => 'edit', $info['User']['id']));

    If you have this link in your layout you will want to specify admin, plugin and controller so the link will work while you are viewing pages created by other controllers. Otherwise if you just specified array(‘admin’ => ‘edit’, $info['User']['id']) and you pulled up /foo/index you would get an error when you click the link because it would take you to /foo/edit/$id.

  11. January 26th, 2009 at 12:49

    Great Job Joseph!!!

    This is the only place that I found a complete solution that works! One of my problems was the user edit form…now the user password just changes if the clear_password and confirm_password are filled.
    And the other problem was the add user form too…now, when i got errors, the password fields remain intact.

    Congratulations and thank you very much!

  12. January 26th, 2009 at 12:55

    @Fernando MSWI
    Thanks for the great comment Fernando. Glad it worked for you!

  13. vijay
    January 27th, 2009 at 01:58

    Really Thanks to you Joseph!!! Its working.

    Now am looking for file uploading using cake php is there any coding for file upload and view?

  14. January 27th, 2009 at 03:06

    Hi, thanks for these useful informations.
    I’m wondering, if there is a way for adding a function just after the login. For example, I would likfe to update a ‘last_visit’ data just after the user logged in succcesfully. Do you have any idea how I can have this with the Auth Magic in the login function? DO I have to rewrite manually the login function?

    Thank you

  15. January 27th, 2009 at 11:50

    Check out the section on file fields in the manual, it should be very helpful. Just don’t forget to change your enctype to multipart/form-data.

  16. January 27th, 2009 at 12:06

    I am afraid you would have to write a custom login function for that. I have seen an example of one somewhere before. Possibly on Mark Story’s website.

  17. Thomas Sebastian
    February 1st, 2009 at 07:55

    Thankyou for the very much for your easy to understand Auth Component Tutorial. I have been tring somany days to solve this problem.At last it was sucessful by your codes

  18. February 4th, 2009 at 08:25

    Well done!!!

    A great tutorial..

    Regards
    Ajay1kumar1

  19. February 4th, 2009 at 15:19

    I’m about to cry I’m so happy. You proved that AuthComponent actually DOES WORK! Now to extend it for LDAP …

  20. February 4th, 2009 at 17:17

    Haha! Good luck with LDAP, it’s not fun :)

  21. February 6th, 2009 at 12:57

    Well, luckily I already had a working LDAP Class that I wrote for PHP, and I’d found some other scripts talking about extending AuthComponent for LDAP, so between that and my LDAP class which I’m using as a vendor to keep the LDAP logic out of the model, everything is working! But you’re right … connecting to LDAP has never been fun. Getting PHP to talk to the LDAP server securely was an even bigger challenge.

  22. February 6th, 2009 at 13:27

    I had to set up authentication for an intranet off active directory back in the cake 1.1 days. I didn’t even attempt to do it over ssl. Glad you got it working though, have a great weekend!

  23. Sparkybarkalot
    February 18th, 2009 at 19:21

    You mentioned an issue with the password hash. In another app I was working on, I was able to use a sha1 hash by:

    1) Putting this in my app controller:

    Security::setHash(’sha1′);

    2) Commenting out the Configure::write(‘Security.salt’ etc… line in core.php

    Was just messing around and it worked just fine, but not sure if commenting out that salt line could be unwise for some reason.

  24. February 18th, 2009 at 19:36

    I guess as long as it doesn’t break anything it should be okay ;)
    I had tried to set Security::setHash(’sha1′); in the app controller but it never seems to grab, maybe commenting out the salt is the trick…or it was just a problem with the beta version I was using at the time.
    Thanks for the comment though, well worth keeping in mind :)

  25. March 4th, 2009 at 08:06

    Hi Joseph ,
    Thanks for the very well done tutorial.
    Is there any possibility to display 3 select boxes using 3 different tables datas in one page.I need ur help.

  26. Stuart
    May 20th, 2009 at 13:15

    In the paragraph entitled “Before Filter” you wrote:

    “You may have noticed the array of methods I passed to Auth component. You will need to uncomment that line in order to add users after enabling the Auth component in your app controller or you will be locked out of your site.”

    I’m not finding anywhere in the article or source code where you passed an array of methods to auth. In the “Other Controllers” paragraph you pass ‘index’ to the auth component in the beforeFilter() callback. I’m a little confused about your meaning.

    I’m trying to make this work with admin routing so it’s a little different than your example.

    Great tutorial though. Thanks!

  27. May 20th, 2009 at 13:30

    I’m sorry I must have deleted that from the example code. The array I mentioned was to allow access to the users pages in order to add your first user. To do so you would need to add $this->Auth->allow(‘*’); to the end of your beforeFilter. After you create your first account remove that line and then log in. Nothing should change much if you are using admin routing. Update the controller function names as well as the view file names. Be sure and update the url in the user add/edit views.
    And you may also need to modify the Auth stuff in the app controller. For example: $this->Auth->loginAction = array(‘controller’ => ‘users’, ‘action’ => ‘login’); would become $this->Auth->loginAction = array(‘admin’ => false, ‘controller’ => ‘users’, ‘action’ => ‘login’); Update logoutRedirect similarly. And the loginRedirect would be $this->Auth->loginRedirect = array(‘admin’ => true, ‘controller’ => ‘users’, ‘action’ => ‘index’);

    Hope that helps, let me know…

  28. anjana
    June 4th, 2009 at 02:43

    Wonderful Tutorial

  29. June 4th, 2009 at 06:08

    Thanks :)

  30. June 10th, 2009 at 19:14

    I’ve posted this to two other old articles and haven’t heard back – glad to see that this is very recent :-) . I am struggling with having the authComponent use email instead of username for login. Cake Version 1.2

    This is what I have in app_controller:
    var $components = array(’Auth’);

    function beforeFilter() {
    $this->Auth->fields = array(
    ‘username’ => ‘email’,
    ‘password’ => ‘password’
    );
    }

    This is what I have in login.ctp:
    echo $form->create(’User’, array(’action’ => ‘login’));
    echo $form->input(’email’);
    echo $form->input(’password’);
    echo $form->end(’Login’);

    I have verified that the registration is working – db is storing users perfectly w/ hashed password. Even after changing the fields in the app_controller before filter, I am getting “Login failed. Invalid username or password.” as the auth flash message.

    Some advice / code related to using email in place of username for the authComponent…? Thanks in advance!

  31. June 10th, 2009 at 21:23

    Hello Bryan,
    What about just using an email address in the username field? I know it kind of throws semantics out the window but if it works?
    It sounds like your situation should work though. They even use the ‘username’ => ‘email’ as an example of Auth->fields in the book. Are you executing the app controllers before filter in your users controller?, like…
    function beforeFilter() {
    parent::beforeFilter();
    }

  32. Abhishek
    June 12th, 2009 at 18:39

    Hi Joseph, thanks for the great tutorial! I am a newbie with cakephp, and this has really helped me understand the Auth component better.

    I had a quick question (which I hope is not too silly). I am currently building a site with two types of users, teachers and students. Each type of user needs to be authenticated and should be stopped from accessing actions meant for the other kind of user. I am not sure how to handle this situation with cakephp, specifically:

    - Is it possible to accomplish this with just the Auth component or is the ACL component needed? Can you ask the Auth component to look at different models depending on the controller?
    - Should I have just one User model with a “type of user” field or is a two model approach (a Student model and a Teacher model) better?

    Im just looking for advice on what approach to take. Thanks very much in advance!

  33. June 12th, 2009 at 21:48

    Hello Abhishek,
    Yes it is possible with the Auth component and only one User model/table. Assuming your groups would not change very often you could add a group field to your users table that has like 3 possible values (‘Admin’, ‘Student’, ‘Teacher’), I added the Admin that would be for like your account…you don’t have to do that. You could also create another table for groups, a Group model, group_id to your users table, and a $belongsTo = array(‘Group’) in your User model.
    However you choose to do that part of it the next part is pretty much the same.
    Add this stuff to your app_controller.php if not there:

    <?php
    class AppController extends Controller {

        var $components = array('Auth');
        var $user;
       
        function beforeFilter() {
            $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
            $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
            $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
            $this->Auth->authorize = 'controller';
            $this->user = $this->Session->read('Auth.User');
        }

    }
    ?>

    And now in every controller in your application you will need an isAuthorized() method which allows you to do some access control.

    <?php
    class ExampleController extends AppController {

        var $name = 'Example';
       
        function beforeFilter() {
            parent::beforeFilter();
        }
       
        function isAuthorized() {
            switch($this->user['group']) {
                case 'Teacher':
                    $permission = true;
                    break;
                case 'Student':
                    switch($this->action) {
                        case 'index':
                            $permission = true;
                            break;
                        case 'add':
                        case 'edit':
                        case 'delete':
                            $permission = false;
                            break;
                    }
                    break;
                case 'Admin':
                    $permission = true;
                    break;
                default:
                    $permission = false;
                    break;
            }
            return $permission;
        }

    }
    ?>

    If all of the methods in your application are pretty standard you could just place an isAuthorized method in your app_controller and cover everything. So that you don’t need one in every controller. Just depends on what you want to do.
    Also you can do anything you want in the isAuthorized method to determine permissions as long as you return true or false in the end. You could also set up the Acl component but that may be a overkill.
    Anyway, hope that helps…best of luck!

  34. Abhishek
    June 15th, 2009 at 04:54

    Oops! I completely overlooked the isAuthorized() method in the Auth component. That makes things very easy, thanks so much for the tip! Is it possible to attach a custom error message when isAuthorized() returns false? Either way, thanks for the help! Cheers from Berlin.

  35. June 15th, 2009 at 09:42

    I like to use setFlash in the session component. I don’t even think you really have to return false. I’ve used redirect to send them to an access denied page before.

    Your lucky to be in Berlin…that means you could easily go to Cakefest this year. I’m jealous, Germany gets all the good *fests, lol

  36. TJ
    July 29th, 2009 at 18:17

    Hi Joseph, I’m new to cake PHP and I wonder why when I try to go to http://localhost/users/add, I would always be redirected to http://localhost/users/login. I am not able to add new users. Anyone also please help?

  37. TJ
    July 29th, 2009 at 18:51

    @TJ
    I missed setting the below at app_controller. With that changed, I am now able to access http://localhost/users/add. Great tutorial btw!
    $this->Auth->allow(‘*’)

  38. Frederick D.
    August 4th, 2009 at 08:22

    This is very good stuff. I especially appreciated the answer to the question from Abisheck on restricting access to actions based on a group_id that a user belongs to.

    Now… with that approach a Student can only do actions applicable to a Student and not a Teacher. Great!

    However, how do we extend this so that a Student can only do actions applicable to their own student number? Also, a Teacher should be able to do actions to their students, but not students of other teachers.

    This extension will help me greatly as I am trying to build an application similar to this but with distributors, dealers, customers, and vehicles. The principles are the same for Teacher (aka Distributor) and Student (aka dealer).

    Thanks in advance for your help!

  39. August 5th, 2009 at 11:11

    @Frederick D.
    Hello Frederick,
    It sounds like you may need to use the ACL Component for your situation. Mark Story has a pretty good article that could help you. I have been planning a tutorial that would fit your need perfectly I think but I’ve just been so busy lately I haven’t had the time to sit down and do it. Feel free to email me though if you run into any problems. jbcrawford at gmail dot com. Also the #cakephp channel on IRC can be pretty helpful.

  40. Frederick D.
    August 8th, 2009 at 12:39

    @Joseph
    I think I just solved it using model relationships. Here’s what I’ve got for relationships:

    Groups->hasManyUsers
    Users->belongsToGroups, hasOneDistributor, hasOneDealer
    Distributors->belongsToUsers, hasManyDealers
    Dealers->belongsToUsers, belongsToDistributors

    When creating a Distributor you select a User.Id for the Distributor row. When creating a Dealer you select a Distributor.Id and a User.Id for the Dealer row.

    Now when a user logs in, and I have their User.Id, when they are in the group=Distributor, I can go grab the applicable Distributor(s) for their User.Id. That also automagically (depending on the setting for recursive) gets me the Dealers associated with the Distributor.

    Using a “conditions” array I either load the User.Id value (for that distributor) or get all (if the user is Admin).

    It is actually pretty cool. So far I have not had to use ACL. I think I can broadly allow/disallow CRUD actions based on the group for my application. For instance, a group=Distributor could use CRUD for their dealers, but no others.

    I think I’m making progress… and in a forward direction! I will be extending the application for Service Advisors that belong to a Dealer and to Customers that will belong to a Dealer.

    I’m impressed with the power of CakePHP to do this so well for me.

    If you like we could perhaps collaborate on a tutorial in the future. I think it is awesome to be able to use the AuthComponent and model relationships instead of ACL.

  41. Adam Parsons
    August 13th, 2009 at 11:22

    function add() {
    if (!empty($this->data)) {
    $this->User->set($this->data);

    if ($this->User->validates()) {
    $this->data['User']['password'] = $this->Auth->password($this->data['User']['new_password']);

    $this->User->save($this->data, false);

    $this->Session->setFlash(__(‘The User has been saved’, true));
    $this->redirect(array(‘action’ => ‘index’));
    } else {
    $this->Session->setFlash(__(‘The User could not be saved. Please, try again.’, true));
    }

    unset($this->data['User']['new_password']);
    unset($this->data['User']['retype_password']);
    }
    }

  42. Adam Parsons
    August 17th, 2009 at 15:11

    function matchPasswords() {
    return $this->data[$this->name]['new_password'] == $this->data[$this->name]['retype_password'];
    }

  43. August 17th, 2009 at 15:35

    What do you keep posting code for?

  44. Sunchaser
    September 5th, 2009 at 14:45

    Thanks for the great tutorial, it make me understand better the Auth component. Maybe you should evidence the following points:

    - the IsAuthorized method should be added at least in the app_controller.php
    - the display action should be allowed in the app_controller.php otherwyse it’s impossible to serve static pages
    - if the app has to show both auth and normal flash messages, in the login page the following code must be added

    check(‘Message.flash’)) {
    $session->flash();
    }
    if ($session->check(‘Message.auth’)) {
    $session->flash(‘auth’);
    }

    Thanks again i was desparate to make the Auth component work.
    ?>

  45. September 15th, 2009 at 13:16

    Hi Joseph!
    Good Post!
    Congratulations. but, im having a problem similar to that reviewed by Bryan.
    I tried what you recommended, but not worked.
    the code is here: http://groups.google.com/group/cake-php/browse_thread/thread/43b0196a16232e2b
    you could give me a hand?

  46. September 18th, 2009 at 00:42

    @Wladiston Paiva
    Hi Wladiston, sorry it has taken me a few days to reply…been super busy. I’m not really sure why you would be getting login failed unless your user information in the database is not correct. I normally keep $this->Auth->allow(*); in my app controller until I can create a new user with each site. This verifies that the hash in the database will match the security salt I set in the config file.

  47. November 13th, 2009 at 05:03

    This is awesome. Especially the update-password-only-if-filled-in part. Great work.

  48. Kris
    December 16th, 2009 at 15:18

    A bit of newbie question here, hopefully you can help out my confusion!?

    ‘required’ => true
    ‘allowEmpty’ => true

    How does this work? the field value is required, and yet is allowed to be empty?

    Aplogises if this is a really dumb question as I still learning! :)

  49. December 16th, 2009 at 15:31

    Hi Kris,
    I’m not sure which model you are referring to. It could either just be thrown in there out of habit so don’t let it confuse you. There are situations where that would be applicable though. If you have a field with multiple validations. You would want to continue added required to each validation rule otherwise not adding it to following rules will cause it to not be required. All ‘required’ really does is add a required class to the div element for styling. So it isn’t even really necessary. I hope that cleared things up for you.

  50. in2lag
    December 21st, 2009 at 05:42

    nice article! after reading lots of posts over the inet, this was the most helpfull. just one question, is there some possibility, how express order of validation rules (because of messages)…

  51. 2 trackbacks
Comment pages
TOP