zen of coding

User Auth with CakePHP 2.1 – part 2

CakePHP 2.1

Now that we’ve completed our basic setup for Auth, let’s take a look at the User.php model…

class User extends AppModel {
   
    public $validate = array(
      'username' => array(
          array(
            'rule' => 'notEmpty',
            'message' => 'Username cannot be empty'
          ),
          array(
            'rule' => 'isUnique',
            'message' => 'This username is already taken'
          )          
      ),
      'password' => array(
          array(
            'rule' => 'notEmpty',
            'message' => 'Password cannot be empty'
          ),
          array(
            'rule' => array('minLength', 4),
            'message' => 'Must be at least 4 chars'
          ),
          array(
            'rule' => array('passCompare'),
            'message' => 'The passwords do not match'
          )
      )
    );
   
    public function passCompare() {
        return ($this->data[$this->alias]['password'] === $this->data[$this->alias]['password_confirm']);        
    }
   
    public function beforeSave() {
        $this->data['User']['password'] = AuthComponent::password($this->data['User']['password']);
        return true;
    }
}

Nothing terribly interesting… we’ve got our validation rules setup and just a few simple methods to handle the rest.
Let’s go in reverse a little. Keep in mind that the new Auth system in CakePHP doesn’t hash passwords by default. This is actually great news for old-timers, because the work-arounds and somewhat hacky solutions we had to do in 1.x (just to deal with password comparison, for example) are gone and are now replaced with a single line of code in your beforeSave() method.
(I hope you see which one). By using AuthComponent::password() we encrypt the user’s password with a default hashing algorithm.
Thus the password is safely hashed in the DB and we do so just prior to saving the record (remember to always return true;) in the beforeSave() or nothing will be… saved).

Speaking of password comparison, you’ll notice that in our validation rules we have a custom method passCompare(). Because I’m lazy and too much typing leads to headaches, a simple one liner will take care of our needs. If passwords match the method will return true, else it will return false and that’s all we really need to validate or invalidate the given field.

For now this covers our User.php model.

Let us take a look at some interesting things in the UsersController.php.

public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow(array(
       'add', 'account_created'
    ));
}

We will allow two methods in our UsersController.php to be accessible by non-authorized (not logged-in) users.
Of course, it would be silly not to allow users to register their account, thus we allow add() method… also we’ll have a simple action account_created(), which is not going to do much of anything for the time being.

Alright, let’s take a look at the relevant add() method:

public function add() {
            if ($this->request->is('post')) {
                if ($this->User->save($this->request->data)) {
                    $this->Session->setFlash(__('Account created. An admin will need to activate it.'), 'default', array(), 'auth');
                    return $this->redirect(array(
                        'controller' => 'users',
                        'action' => 'account_created'
                    ));
                }
            }
        }

All we do here is accept the data from a form, validate it (by using the save() method) and if all goes well, we redirect the user to the “account created” page.
Remember all that prefix/admin routing we’ve setup before?
Well, as you can see our user is going to live in the system, but remain inactive until an admin logs-in and activates her. Recall ‘scope’ => array(‘User.is_active’ => 1)… freshly created account will have is_active = 0, so without admin approval nobody is getting in.

Let’s take a quick look at the add.ctp view:

<?php
    echo $this->Form->create();
    echo $this->Form->inputs(
        array(
            'username',
            'password',
            'password_confirm' => array(
                'type' => 'password'
            )
        )
    );
    echo $this->Form->end('Submit');
?>

As you can see I really went all out here… three fields: username, password, and password_confirm. Now compare these fields to the validation rules in our User.php model and you should see how the whole thing is coming together. I kept the example purposely oversimplified to just show how the data will be POST’ed from the form to the controller’s add() method, validated by the User.php model (with password hashing) and thereafter saved to the DB.

To wrap things up for part 2 of this tutorial, let’s take a look at the login() and logout() methods (also in the UsersController.php).

public function login() {
            if ($this->request->is('post')) {
                if ($this->Auth->login()) {
                    if ($this->Auth->user('is_admin')) {
                        return $this->redirect(array(
                            'controller' => 'users',
                            'action' => 'index',
                            'admin' => true
                        ));
                    } else {
                        return $this->redirect('/');
                    }
                } else {
                    $this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
                }
            }
        }

The interesting thing about this method is the check for admin. if ($this->Auth->user(‘is_admin’)) { … , so if the user is an admin we redirect them to example.com/admin/users/index (this is all part of prefix/admin routing). To tell cake, which “route” it should take we supply ‘admin’ => true. Of course, this presumes that we’ll have an admin_index() method in the UsersController.php. As you see, all other users simply get redirected to the “/” (root) of your website.

The logout() method is as simple as could be:

public function logout() {
            $this->redirect($this->Auth->logout());
        }

In the next part we will take a look at the admin side of things and some little things like a login/logout link, which you’d expect in any site that has user Auth.

  • Rafael

    Thanks from Brazil :D

  • Jav

    I am creating a simple app to test cake and don’t want to create users via a view. I added some users to my user table, which doesn’t have ‘is_admin’ or ‘is_active’ fields, so I omitted that from the login function. However, when I try and log in, it’s always failing and brings me right back to the login page. Any advice, please?

    Thx

  • teknoid

    @Jav

    Please take a look at part one… make sure you do not have user scope defined (since you don’t have the ‘is_active’ field).

%d bloggers like this: