I got a question in the comments about my previous post on simple user registration about how to do some of the necessary validation for registration in the model. I thought I'd show some code I did to do exactly that.
The key to all this stuff is using a second form field for doing the validation. Here's some sample code for you, based on the latest straight-from-svn version of Cake PHP 1.2 (r6402)
-
<?php
-
-
/**
-
* Class used for user authentication on the league website
-
*
-
*/
-
-
class User extends AppModel
-
{
-
var $name = 'User';
-
-
'on' => 'create'),
-
'required' => true,
-
'message' => 'Please enter a username'),
-
'message' => 'Passwords do not match'),
-
'required' => true)
-
);
-
-
function confirmPassword($data) {
-
$valid = false;
-
-
if ($data['password'] == Security::hash(Configure::read('Security.salt') . $this->data['User']['password_confirm'])) {
-
$valid = true;
-
}
-
-
return $valid;
-
}
-
-
}
-
?>
So, let's talk about what's in there.
- make sure that the username is alphanumeric and has been entered
- make sure the password exists and run the custom validation function 'confirmPassword' on the data being posted in
- make sure that our confirm password field exists and is alphanumeric
The only tricky thing when I made this was figuring out how to compare the two password fields, and where to get the proper hashing from. Initially I thought that I could somehow import the Auth component in there but a quick chat with gwoo showed me how stupid that was when I could just duplicate how the component itself is hashing the password field. That's what is going in with the use of Security::hash(...).
Hope that helps.
Tags: auth, CakePHP

Hi Chris,
I think it means you can enter an empty password and an empty confirm password too. Hash will give same results, and user can log with empty password ... Maybe I am wrong ... to verify..
Cheers
@francky06l
You can't, because the validation requires that there be info in the confirm password field. I just tried it in my test app.
Thanks Chris, actually it's was I was wondering from version 1 .. Good tutorial !!!
Cheers
Grat tuto Chris, it answers lots of posts in the group
Hi, why the second param $fieldName at function confirmPassword($data, $fieldName)?
Thanx for the "tuto"!
@Simon:
Consider this: http://bakery.cakephp.org/articles/view/using-equalto-validation-to-compare-two-form-fields
Comment 7. states that we can shorten the validation function. So, something like this, should work for you:
function confirmPassword( $field, $fieldName=null ) {
return($this->data[$this->name][$fieldName] === array_shift($field));
So, in ur case $data['password'] can be replaced with array_shift($data) // It's only gonna have one in it anyways.
And $this->data['User']['password_confirm'] can be replaced with: $this->data[$this->name][$fieldName].
Now there is one slight thing you would need to change:
'rule' => array('confirmPassword', 'password')
would need to be:
'rule' => array('confirmPassword', 'confirmPassword')
Is this a typo Chris?
@Baz
No, it's not a typo. I'm wanting to compare the hashed password being provided by Auth to a hashed version of the confirmPassword field. The Auth component will hash the contents of the 'password' field BEFORE any validation gets run.
@Simon is right that you can get rid of that extra $fieldName parameter. I just tried it and it works just fine. I'll update the code in the post to reflect that.
@Chris
I know, but from my understanding of the custom Validation things should work as follows:
- you set the rule on 'password' and the parameter u pass to the function should be 'confirm_password'.
- the function would be defined as: confirmPassword($data, $field) where $data would contain an array with the hashed value of 'password' and $field would be the parameter passed in the rule definition: 'confirm_password'.
But, since you changed it and dropped the 2nd parameter (that's why i said typo. it didn't seem to be used), we're all good.
$this->data[$this->name][$field]
as apposed to $this->data['User']['password_confirm'].
@Baz
Actually, the way it's working is like this:
- 'password' has a custom validation function called 'confirm_password'
- $data['password'] contains the value of 'password' that has been hashed by the Auth component
- I then compare what is in $data with what the contents of the 'password_confirm' field would be if it was hashed
Now, am I doing it the 100% correct way? I have no idea, but I *do* know that it works to make sure that what I've entered in the password field matches what I entered in the password_confirm field.
In this case I need it to work first, and then I can go back and tweak it if I discover there is a better way to do it.
Chris,
Thanks for this handy information. I'm learning Cake 1.2 and finding things are often less than crystal clear, so I'm glad others have figured out a lot of this stuff before me.
One change I made that might be helpful: in the view for my signup form, I set the password_confirm error to be printed below the password field, and the password error to be printed below the password_confirm field, since the password validation results in a password-confirmation error message ("do not match") and the confirmation match results in a password error message ("must be alphanumeric").
That's kind of a confusing description, but it ends up putting the error messages where they make the most sense.
to those who dont bother digging into the code, password hash algorithm is: $hashedPass=Security::hash(Configure::read('Security.salt') . $password))
I like to hash the password and the confirm password in the same place using Auth's hashPassword callback.
I've made a small post about it in my blog: http://www.gignus.com/blog/posts/view/16
Hey Chris thanks for this information! I've only been using Cake for about two weeks now and of course the first thing I do is go to try to make a login page... which resulted in never being able to validate password consistency. This was very helpful!
The only problem I had with this (and still have) is, if the validation fails, the form reloads with the already-encrypted password instead of the original input. I haven't found a way around this. One option would be to temporarily turn auth off during registration, but I'm not sure of the best way to do this programatically.
If I just turn it off altogether, the password gets saved in plain text. I imagine there must be some hook to alter data before it's sent to the db, but I haven't researched that yet.
I'm new to Cake, just started playing with it the last couple days.
As a quick follow-up - in the Cake Google group I saw a discussion that suggested the only workaround is to zero out the password fields anyway on reload, since they aren't human-readable in the form anyway...
@Derek
I think the best way to handle this is to simply remove that from $this->data if the save fails. I think something like this might work
...
if ($this->User->save($this->data)) {
// everything is ok
} else {
unset($this->data['User']['password'];
}
I haven't checked it, but that should do the trick. Let me know either way.
Thanks for the quick response. That works in a way, but it has the side effect of removing the data even if the validation error wasn't because of the password. So if for example I enter valid matching passwords but a bad email address, the reloaded form shows empty password fields.Not a huge deal, that's the kind of thing that can drive a user crazy on a long form where the errors are at the top but the password field is at the bottom.
I came up with a hacky workaround in the view, which I just now discovered might work in the view. First, query $this->User->invalidFields() to see if the error was a password error. If so, unset those fields. Else reset them to match the $_POST array values:
$password_error = array_key_exists('password_match',$this->User->invalidFields()) || array_key_exists('password',$this->User->invalidFields());
if($password_error){
unset($this->data['User']['password']);
unset($this->data['User']['password_match']);
} else {
$this->data['User']['password'] = $_POST['data']['User']['password'];
$this->data['User']['password_match'] = $_POST['data']['User']['password_match'];
}
I had been doing this in the view but much prefer doing it in the controller. I suppose it could also be done in the model but I'm not sure where to insert it. I suppose I could override save() and add it if the save fails.
...and I've confirmed that it works to override save(). Here's what class User looks like. Not that I use password_match instead of password_confirm for the field name, and that the first argument to save is passed by reference.
function save(&$data,$validate=true,$fieldlist=array()){
if(parent::save($data,$validate,$fieldlist)){
return true;
}
//if save fails, reset passwords
$password_error = array_key_exists('password_match',$this->invalidFields()) || array_key_exists('password',$this->invalidFields());
if($password_error){
unset($data['password']);
unset($data['password_match']);
} else {
$data['password'] = $_POST['data']['User']['password'];
$data['password_match'] = $_POST['data']['User']['password_match'];
}
}
I wonder why that validation doesn't work if I define the password field first and check if it has a minLength of 6 chars and then I check the confirm_password. It doesn't display the minLength errer, however if I write it vice versa - like you above it works great. It doesn't make much sense to me.
@Derek
IMO, you should NEVER reload the password. The user should always have to re-enter the password, regardless of the reason why the form was forced to reload. Password entry should never be about convenience, only about security.
I think you miss the point with not needing to reenter the password when doing validation. If I'm registering for a site, and I successfully entered my password twice, but didn't pass some other validation (e.g. - fat fingered the email address), then I shouldn't need to reconfirm my password again.
The problem I've been seeing with the Cake Auth module is that it replaces the value of the password with the hash, so that on a subsequent resubmit, the two fields will never match.
IMHO, if I have a registration form, and the form errors out for some reason besides the passwords not matching, the data in the form shouldn't get changed so that the user can address that error. Instead we end up frustrating the user by allowing them to correct the error and then failing again due to a password mismatch.
@Rob
What you're suggesting is actually very easy to fix: if validation fails overall but the password validation passed then simply replace the contents of the password field with what is in the password field. Not a Cake problem, a developer problem.
OK, not sure I understand that. From what I've seen, the data gets hashed before it is saved, so I don't follow what you mean by 'replace the contents of the password field with what is in the password field'.
If I type in 'thisIsMyPassword', it gets turned into a hash. Then if the validation fails, how do I reset it to 'thisIsMyPassword' instead of ending up hashing the hash? Or are you saying that this is a one way hash where the hash will yield the same value as the original password?
I'm definitely a newbie to this framework, so I apologize for my confusion.
@Rob
Sorry, it was a typo on my part. What I *meant* to say was this: if validation fails, but the password validation was okay, you replace what is in the *password* field with what was in the *password_confirm* field. Sorry for the mixup. I haven't tried that, but it seems to me that it should work just fine.
Thanks, that helps. Now I've run into another newbie issue with the code as discussed above.
Since the password_confirm field validation says that it is required, if I create another view to update the users table, I get a validation failure on update.
Is there a way to register the validation so that the password_confirm field is only required for the register action of the controller ?
@Rob
The easiest way I've found to do that is to use James Snook's Multiple Validatable behavior http://snook.ca/archives/cakephp/multiple_validatable_behavior/
I've used that in project where I had two different views manipulating the same data but needed different validation rules. Hope it helps.
I'm totally splitting hairs here, but you could shorten the confirmPassword method into one line:
function confirmPassword($data) {
return ($data['password'] === Security::hash(Configure::read('Security.salt').$this->data['User']['password_confirm']);
}