Yii 1.1: Model password confirmation field.

23 followers

I had some troubles with the password confirmation field for when adding updating user records, so i thought that i should share the way i got it working.

The scenario is the basic one, you have a database table (say user) and this table has a field called password, which is a sha1/md5/etc hash of the user password.

This is the workflow:
When you create a new user, the password needs to be hashed and saved, but when you update a user record, if the same scenario happens, we end up with a hash of the user hashed password, and we don't want this. Instead, on update, we will empty the user password from the model object, store it temporary in another variable then check to see if the password has been submitted in the form, if it was, it means the user password needs to be updated, therefore we need to hash the password(which is plain text now), if it wasn't submitted, then it means it doesn't need to be updated therefore, we restore it from the temporary variable.

So, here we go, the model:

<?php if ( ! defined('YII_PATH')) exit('No direct script access allowed');
 
class User extends CActiveRecord
{
    // holds the password confirmation word
    public $repeat_password;
 
    //will hold the encrypted password for update actions.
    public $initialPassword;
 
    /**
     * @return array validation rules for model attributes.
     */
    public function rules()
    {
        // NOTE: you should only define rules for those attributes that
        // will receive user inputs.
        return array(
            //password and repeat password
            array('password, repeat_password', 'required', 'on'=>'insert'),
            array('password, repeat_password', 'length', 'min'=>6, 'max'=>40),
            array('password', 'compare', 'compareAttribute'=>'repeat_password'),
 
        );
    }
 
 
    public function beforeSave()
    {
        // in this case, we will use the old hashed password.
        if(empty($this->password) && empty($this->repeat_password) && !empty($this->initialPassword))
            $this->password=$this->repeat_password=$this->initialPassword;
 
        return parent::beforeSave();
    }
 
    public function afterFind()
    {
        //reset the password to null because we don't want the hash to be shown.
        $this->initialPassword = $this->password;
        $this->password = null;
 
        parent::afterFind();
    }
 
    public function saveModel($data=array())
    {
            //because the hashes needs to match
            if(!empty($data['password']) && !empty($data['repeat_password']))
            {
                $data['password'] = Yii::app()->user->hashPassword($data['password']);
                $data['repeat_password'] = Yii::app()->user->hashPassword($data['repeat_password']);
            }
 
            $this->attributes=$data;
 
            if(!$this->save())
                return CHtml::errorSummary($this);
 
         return true;
    }
 
}

When the user is created, we do it with the "insert" scenario, meaning that the password is required, but when we update it, we do it with the "update" scenario, meaning that the password is not required anymore, therefore when the form is submitted, the password fields can be empty, and the validation won't fail. This allows us to restore the hashed password from the temporary variable.

Just as a side note, here is how my controller methods looks:

public function actionCreate()
    {
        $user=new User('insert');
 
        $this->saveModel($user);
 
        $this->setViewData(compact('user'));
        $this->render('create', $this->getViewData());
    }
 
public function actionUpdate($id)
    {
 
        $user=$this->loadModel($id);
        $user->scenario='update';
 
        $this->saveModel($user);
 
        $this->setViewData(compact('user'));
        $this->render('update', $this->getViewData());
    }
 
protected function saveModel(User $user)
    {
        if(isset($_POST['User']))
        {
            $this->performAjaxValidation($user);
            $msg = $user->saveModel($_POST['User']);
            //check $msg here
        }
    }

And this is the rendering form:

<section>
        <?php echo $form->labelEx($user,'password'); ?>
        <div>
            <?php echo $form->passwordField($user,'password',array('maxlength'=>40)); ?>
            <?php echo $form->passwordField($user,'repeat_password',array('maxlength'=>40)); ?>
        </div>
        <?php echo $form->error($user,'password'); ?>
    </section>

Hope it helps :)

Chinese Version

Total 10 comments

#17522 report it
Rohit Suthar at 2014/06/27 09:42am
Updated tutorial with old, new and repeat password

For more security, required also old password, new password and repeat password.

http://www.yiiframework.com/wiki/718/change-password-with-tbactiveform

#14409 report it
twisted1919 at 2013/08/09 04:50am
Nope

Instead of bitching around, you are welcome to edit the entry or create a new one with a better approach, I'm sure you can come up with something better ;)

#14408 report it
Heyho at 2013/08/09 04:39am
Title cannot be blank.

If you intend it as an example, use an example class, and not a framework class.

#14407 report it
twisted1919 at 2013/08/09 04:03am
Do your homework

@heyho - you do realize this is just an example, right? I have no idea how one prefer to hash the password, so that was just a plain example:) Most of the people reading this got the point, except you ;)

#14405 report it
Heyho at 2013/08/09 03:01am
Wrong. Wrong, wrong, wrong!

I have no idea why you think

$data['password'] = Yii::app()->user->hashPassword($data['password']);

is the correct thing to do, when the result of this will be

Exception Saving Data: CWebUser and its behaviors do not have a method or closure named "hashPassword".

#9597 report it
IainG at 2012/08/27 06:27pm
Simplified version

Thanks for posting this - I've used it as a starting point and come up with a simplified version. Here's how I've done it:

  • password_hash field in the database
  • password and repeat_password attributes in the User model
  • scenario validation rules as in your example above

Then in my model beforeSave(), I just have:

//add the password hash if it's a new record
        if ($this->getIsNewRecord())
        {
            //creates the password hash from the plaintext password
            $this->password_hash = Yii::app()->user->hashPassword($this->password);                         
        }       
        else if (!empty($this->password)&&!empty($this->repeat_password)&&($this->password===$this->repeat_password)) 
        //if it's not a new password, save the password only if it not empty and the two passwords match
        {
            $this->password_hash = Yii::app()->user->hashPassword($this->password);
        }

Yii::app()->user->hashPassword() is a custom hashing function I got from another wiki article here.

#8781 report it
marcovtwout at 2012/06/26 11:14am
About scenario's

Default scenario when you create a model with the new keyword is insert, and when you do find() the scenario is update. No need to set them again. ;)

http://www.yiiframework.com/doc/api/1.1/CActiveRecord#__construct-detail http://www.yiiframework.com/doc/api/1.1/CActiveRecord#populateRecord-detail

#6107 report it
twisted1919 at 2011/12/13 08:26am
from mail

Got an email for this article:

In your afterFind() function, you reset the password to null. How did you deal with that in your UserIndentity.php file? Before attempting to use your code, my logins were working just fine with this code:
 
if($user->password!==$user->encrypt($this->password))
{
     $this->errorCode=self::ERROR_PASSWORD_INVALID;
}
else
{
     $this->_id = $user->id;
     etc...
 
but since password is null, my login now fails. Any ideas?

For who is having this issue, the login form must use a CForm model and define the rules() for the login fields there, also the validation will be made by the CForm model, and if the validation passes, instantiate a new User and assign it the username/email and password.

#6066 report it
twisted1919 at 2011/12/09 07:36am
Share the model

Please use something like pastebin.com and share your model so that i can take a look at.

L.E: also please make sure that in your controller you set the scenario to "update" after you load the model.

#6061 report it
Tahir Yasin at 2011/12/09 12:39am
both passwords are required in update case also

Great effort.. I followed your code but both passwords are also required in update case, please help me.

Thanks

Leave a comment

Please to leave your comment.

Write new article