Sean’s Obsessions

Sean Walberg’s blog

A Simple Authentication System With CakePHP 1.2 and Auth Component

I’ve been learning the CakePHP framework recently, and came to need a simple user login system.

Judging by the documentation out there, ACLs are the way to do it. However after spending an hour trying to figure out all the contradicting articles out there I gave up. ACLs are very precise, all I need is a simple “Sean is logged in” type of thing.

More reading on the Bakery pointed me to the Auth component, which does exactly what I need. Again though, I found incomplete documentation, which combined with reading some of the code and trial and error, I figured it out. What I ended up with was a system that:

  1. By default requires users to log in with a username (email) and password
  2. Guests trying to access a protected resource get redirected to the login screen
  3. Controllers can determine which methods need protection and which don’t
  4. Supports basic groups - registered users are “users”, admins are “admins”. The controller figures out the rest based on that string
  5. It’s easily understood, and doesn’t require a lot of code in each controller


So to start I need a user model:

1
2
3
4
5
6
7
8
9
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`email` varchar(255) NOT NULL,
`password` char(32) NOT NULL,
`role` varchar(20) NOT NULL default 'user',
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

The corresponding model code (user.php) is:

1
2
3
4
5
6
7
8
9
10
11
12
13
class User extends AppModel {
   var $name = 'User';
   var $validate = array(
       'email' => VALID_NOT_EMPTY,
       'password' => VALID_NOT_EMPTY
   );
   function beforeSave() {
       if ($this->data['User']['password']) {
            $this->data['User']['password'] = md5($this->data['User']['password']);
       }
       return true;
  }
}

As a password is written into the database, it is automatically md5’ed.

Because I want authentication on every page (with exceptions… hang in there) it’s done at the cake/app_controller.php level in a beforeFilter hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AppController extends Controller {
        var $components = array("Auth");
        function beforeFilter() {
                // Handle the user auth filter
                //  This, along with no salt in the config file allows for straight
                // md5 passwords to be used in the user model
                Security::setHash("md5");
                $this->Auth->fields = array('username' => 'email', 'password' => 'password');
                $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
                $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'myprofile');
                $this->Auth->logoutRedirect = '/';
                $this->Auth->loginError = 'Invalid e-mail / password combination.  Please try again';
                $this->Auth->authorize = 'controller';
        }

All the $this->Auth lines fill in the various behaviours, such as what the login page is. Setting “authorize” to “controller” means each controller must have an isAuthorized() function that returns true or false if the already authenticated user is authorized to do the action. The key was setting the hash to MD5, because the default is SHA1 and that wasn’t working with my model I made earlier. You also must edit your config file to remove the salt. This behaviour may be recent though, I see some reports that the Auth component uses plaintext passwords which is untrue in HEAD.

With this in place, all pages will require authentication. To fix that, in the controllers you want to be open, add a beforeFilter:

1
2
3
4
 function beforeFilter() {
                $this->Auth->allow("*");
                parent::beforeFilter();
        }


This will allow all actions in the controller to pass without authentication. allow() seems to take a list of actions that don’t need authentication, * means all. If only a few actions needed authentication, I’d do $this->Auth->allow(“view”, “foo”) to allow the view and foo actions to be open.

If you need to see the user that is logged in, use $this->Auth->user(), which returns the model. From there I can get the role/group, or whatever else I put in there. It doesn’t seem you can set() variables for the view in the beforeFilter, so if you want the view to have part of the User model you should set() it elsewhere.

For my login function, I stole it directly from this tutorial. (Actually most of what I did was based on that tutorial, my contribution is really the explanation of the MD5 stuff and the allow() action (the latter which I now see is in his tutorial, just buried down in another section))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   if ($this->Auth->user()) {
                        if (!empty($this->data)) {
                                if (empty($this->data['User']['remember_me'])) {
                                        $this->Cookie->del('User');
                                } else {
                                        $cookie = array();
                                        $cookie['email'] = $this->data['User']['email'];
                                        $cookie['token'] = $this->data['User']['password'];
                                        $this->Cookie->write('User', $cookie, true, '+2 weeks');
                                }
                                unset($this->data['User']['remember_me']);
                        }
                        $this->redirect($this->Auth->redirect());
                }

The rest of the code is fairly simple, a register action to set up an account, and a logout function to call $this->Auth->logout.

Comments

I’m trying something new here. Talk to me on Twitter with the button above, please.