22 Jan
Simple User Registration in CakePHP 1.2, Part II
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.
Article Tags >> auth || CakePHP
Posted by francky06l on 22.01.08 at 11:54 am
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
Posted by Chris Hartjes on 22.01.08 at 11:54 am
@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.
Posted by francky06l on 22.01.08 at 11:54 am
Thanks Chris, actually it's was I was wondering from version 1 .. Good tutorial !!!
Cheers
Posted by Djiize on 22.01.08 at 11:54 am
Grat tuto Chris, it answers lots of posts in the group
Posted by Simon Brüchner on 22.01.08 at 11:54 am
Hi, why the second param $fieldName at function confirmPassword($data, $fieldName)?
Thanx for the "tuto"!
Posted by Baz L on 22.01.08 at 11:54 am
@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?
Posted by Chris Hartjes on 22.01.08 at 11:54 am
@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.
Posted by Baz L on 22.01.08 at 11:54 am
@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'].
Posted by Chris Hartjes on 22.01.08 at 11:54 am
@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.
Posted by Ryan Freebern on 22.01.08 at 11:54 am
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.
Posted by Robert Meisner on 22.01.08 at 11:54 am
to those who dont bother digging into the code, password hash algorithm is: $hashedPass=Security::hash(Configure::read('Security.salt') . $password))
Posted by Matt on 22.01.08 at 11:54 am
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
Posted by Cody Sortore on 22.01.08 at 11:54 am
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!
Posted by Derek Scruggs on 22.01.08 at 11:54 am
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.
Posted by Derek Scruggs on 22.01.08 at 11:54 am
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...
Posted by Chris Hartjes on 22.01.08 at 11:54 am
@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.
Posted by Derek Scruggs on 22.01.08 at 11:54 am
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.
Posted by Derek Scruggs on 22.01.08 at 11:54 am
...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'];
}
}