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:
  1. <?php
  2.  
  3. /**
  4. * Class used for user authentication on the league website
  5. *
  6. */
  7.  
  8. class User extends AppModel
  9. {
  10.     var $name = 'User';
  11.  
  12.     var $validate = array(
  13.         'id' => array('rule' => 'blank',
  14.                       'on' => 'create'),
  15.         'username' => array('rule' => 'alphanumeric',
  16.                             'required' => true,
  17.                             'message' => 'Please enter a username'),
  18.         'password' => array('rule' => array('confirmPassword', 'password'),
  19.                             'message' => 'Passwords do not match'),
  20.         'password_confirm' => array('rule' => 'alphanumeric',
  21.                                     'required' => true)
  22.     );
  23.  
  24.     function confirmPassword($data) {
  25.         $valid = false;
  26.        
  27.         if ($data['password'] == Security::hash(Configure::read('Security.salt') . $this->data['User']['password_confirm'])) {
  28.             $valid = true;
  29.         }
  30.        
  31.         return $valid;
  32.     }
  33.  
  34. }
  35. ?>

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 >> ||

18 Responses to this post.

  1. francky06l's Gravatar

    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

  2. Chris Hartjes's Gravatar

    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.

  3. francky06l's Gravatar

    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

  4. Djiize's Gravatar

    Posted by Djiize on 22.01.08 at 11:54 am

    Grat tuto Chris, it answers lots of posts in the group

  5. Simon Brüchner's Gravatar

    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"!

  6. Baz L's Gravatar

    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?

  7. Chris Hartjes's Gravatar

    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.

  8. Baz L's Gravatar

    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'].

  9. Chris Hartjes's Gravatar

    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.

  10. Ryan Freebern's Gravatar

    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.

  11. Robert Meisner's Gravatar

    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))

  12. Matt's Gravatar

    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

  13. Cody Sortore's Gravatar

    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!

  14. Derek Scruggs's Gravatar

    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.

  15. Derek Scruggs's Gravatar

    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...

  16. Chris Hartjes's Gravatar

    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.

  17. Derek Scruggs's Gravatar

    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.

  18. Derek Scruggs's Gravatar

    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'];
    }

    }

Respond to this post

Want to advertise on this blog? Send email to chartjes@littlehart.net
GTcars Canadian Car Audio TurboDodge Car For Sale Sign