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
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 ;-)
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'))?>
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.