Stop ACTA

Manually hashing password and password validation in CakePHP

Posted in CakePHP on 08.02.2009.

Sometimes you want to go avoid Cake's automagic password hashing for miscellaneous purposes, like additional validation, password strength tests and other things. This is very easy to accomplish.

Let us assume that you have a simple user registration form and you want to validate the password's length, girth and volume. This might seem like an obvious task, simply define the validation rules in your model and voila!

But this is not as straightforward when you're using the built-in AuthComponent. Auth tends to hash your password before you even get the chance of looking under it's skirt. To avoid this problem, we need to trick the Auth component into thinking there is no password, validate it, and then hash it ourselves.

To accomplish this, we do the following:

  • Change the name of the input from "password" to "passwd"
  • Define validation rules for the new "passwd" field
  • in beforeSave() hash the password value and store it in the real field

One obvious advantage of this method is that now you can validate the length of password, and also add the "passwd_confirm" field, for password confirmation. That will be our example here.

Onwards to code. Change the code on your form from this:

echo $form->input('User.password');

To this:

echo $form->input('User.passwd');
echo $form->input('User.passwd_confirm');

That effectively eliminates the annoying Auth habit of hashing your password. Next thing on the list is to define some validation rules for the new fields. For this example, I'm going to use a simple validation rule to compare "passwd" and "passwd_confirm":

class User extends AppModel
{
    function __construct()
    {
        parent::__construct();

        $this->validate = array
        (
            /* snip other fields */
            'passwd_confirm' => array
            (
                /* snip other rules */
                'match' =>
                array
                (
                    'rule'          => 'validatePasswdConfirm',
                    'required'      => true,
                    'allowEmpty'    => false,
                    'message'       => __('Passwords do not match', true)
                )
            )
        );
    )

    /* snip */

    function validatePasswdConfirm($data)
    {
        if ($this->data['User']['passwd'] !== $data['passwd_confirm'])
        {
            return false;
        }

        return true;
    }
}

Now that we have set up the validation and avoided the Auth hashing, the only thing left to do is hash the password our self. The place to do it: beforeSave() of course.

function beforeSave()
{
    if (isset($this->data['User']['passwd']))
    {
        $this->data['User']['password'] =
            Security::hash($this->data['User']['passwd'], null, true);
        unset($this->data['User']['passwd']);
    }

    if (isset($this->data['User']['passwd_confirm']))
    {
        unset($this->data['User']['passwd_confirm']);
    }

    return true;
}

The most important line here is the following:

Security::hash($this->data['User']['passwd'], null, true);

This line makes the password readable by the Auth component, and that in turn removes the need of creating your own. Take a look at the API reference of Security::hash() and you will see that the third parameter is very important, as it tells the hash function to use the "Security.salt" value in the hashing process. In any case, if you need to hash something and be compatible with the Cake's own Auth and Security, this seems to be the right way.

I hope this will help someone with their user registration form..

Happy baking ;-)

Article comments — View · Add


Page 1 of 4
1 · 2 · 3 · 4

Abba Bryant :: 09.02.2009 17:33:45
Mind if I ask why you don't use the more recognized ( less php5ish ) method of simply assigning the public $validate = array( ... ); method of assigning your validation array?
lecterror :: 09.02.2009 17:37:54
Hi,

I'm forced to do it this way because of the gettext calls e.g.:

__('Passwords do not match', true)

Which cannot be used in a variable declaration. Hope that clears it up ;-)
amit :: 22.02.2009 15:20:37
Faced the same problem and your article helped a lot, thanks
Antonio Kobashikawa Carrasco :: 08.03.2009 13:42:37
Hi, thank you for your post, I use this aprox.
However, about use __construct() to declare validation rules because you can't use __() when define $validate, I use $validate in usually way, and __() not in model else in the controller, as in:

user.php
var $validate = array(
'user'=>array(
'pattern'=>array(
'rule'=>array('custom', '/^[0-9A-Za-z\_\-]{5,255}$/'),
'required'=>true,
'message'=>'Only alphanumeric characters please'
)
)
)

add.ctp
<?php __($form->error('User.name'))?>
lecterror :: 08.03.2009 14:51:36
Hi!

I understand why you want to it like that (much cleaner and backwards compatible), but I don't believe that your strings will be extracted by cake i18n shell when used that way.

I believe that makes it a bit more complicated to maintain.