RSS
 

Archive for August 25th, 2011

Working with passwords in CakePHP

25 Aug

A clean approach for working with passwords

Most beginners make the mistake to use the baked templates and “password” as fieldname for the database field “password”. But this causes quite some issues. For starters it will also hash and save empty strings (no input) and mess up previously entered passwords. So you should never use this field in the forms. Always use “pwd” or some other temporary field with you will then map the real field in your model’s beforeSave() method:

public function beforeSave($options = array()) {
    parent::beforeSave($options);
    if (!empty($this->data[$this->alias]['pwd'])) {
        $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['pwd']);
    }
    ...
    return true;
}
The main advantage of ALWAYS using a different name (even with cake2) is that you cannot accidently write an unhandled/unhashed password to your database. Also consider providing a “pwd_repeat” field to ensure the entered password was typed correctly – especially on the user side. To keep this easy and extendable use the following behavior.

Introducing my ChangePassword behavior

The code can be found on github (CakePHP2.0) [1.3]. What it does is taking care of all the stuff that usually is done redundant in several places: hashing, validating, whitelisting, ensuring fields, … Using the behavior the controller code stays slim (remember: fat model, slim controller).

You can override the default values with Configure::write('ChangePassword.auth', 'MyAuth'); for example (config/bootstrap). It is also possible to change them at runtime – where you include the behavior. It is important NOT to globally assign the behavior to the model – for security purposes. Only use it in those actions that actually work with the password (update/reset/…). This way other actions cannot accidently use the behavior to update the password (or by tempering with the forms).

PWD_MIN_LENGTH and PWD_MAX_LENGTH are constants and can be set in the bootstrap.

Admin Forms

if ($this->request->isPost() || $this->request->isPut()) {
    $this->User->Behaviors->attach('Tools.ChangePassword', array('confirm'=>false, 'allowEmpty'=>true));
    if ($this->User->save($this->request->data)) {
        //YEAH
    } else {
        //OH NO
    }
}
As admin I do not want to retype the password. So confirm is set to false. Allowing empty passwords can be set as well (although not recommended).

Change password form for the user

if ($this->request->isPost() || $this->request->isPut()) {
    # attach the behavior and force the user to enter the current password for security purposes
 $this->User->Behaviors->attach('Tools.ChangePassword', array('current'=>true));
    $this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
    if ($this->User->save($this->request->data, true, array('pwd', 'pwd_repeat', 'id'))) {
        //SUCCESS flash/redirect
    }
    //ERROR flash
 
    # pw should not be passed to the view again for security reasons
 unset($this->request->data['User']['pwd']);
    unset($this->request->data['User']['pwd_repeat']);
}
As user I have to first provide the old password and confirm (retype) the new password. Confirmation is active by default.

Note: this is an own change_pw action. But it could as well a complete edit form. Please make sure, though, that you set the white list to only those fields that are allowed to be updated (or use Security component). Adding pwd and pwd_repeat etc should not be necessary as the behavior takes care of adding them to the whitelist.

Register forms

Quite similar to the user edit forms. Only without the user_id part, of course. It boils down to:

if ($this->request->isPost() || $this->request->isPut()) {
    $this->User->Behaviors->attach('Tools.ChangePassword', array('current'=>false));
    if ($this->User->save($this->request->data, true, array('pwd', 'pwd_repeat', ...))) {
        //SUCCESS flash/redirect
    }
    //ERROR flash
    unset($this->request->data['User']['pwd']);
    unset($this->request->data['User']['pwd_repeat']);
}

Login

In login forms some forget to clear the password after an unsuccessful post.

if ($this->request->isPost() || $this->request->isPut()) {
    if ($this->Auth->login()) { # for 1.3 use ->user() here
     ...
    }
    # the important part after every post:
 $this->request->data['User']['password'] = '';
}
Now the password field stays empty. Which is usually a good thing to do in forms. The ChangePassword behavior is not needed here as the Auth component already takes care of the login procedure. That’s why “password” as fieldname is all right in this case.

Some tricky parts

Maybe you wondered about “nonEmptyToEmpty”. Well, if you do allow empty passwords and you simply add pwd to the form fields you would always override any (maybe non empty) password with the hashed empty string. Therefore the behavior does by default not allow empty passwords once a (nonempty) password is set. You can – if you design your edit forms carefully – set this to true.

Notes

In Cake1.3 the framework automatically hashes the field “password”. that’s one of the reasons why you should use “pwd” or something similar and either let the behavior/model handle it or assign “pwd” to “password” manually on save. In Cake2.0 no automatic hashing will be done anymore. But it will still be useful not to use “password”. Especially because the validation rules would not apply to a sha1 string etc^^

For resetting local passwords for development purposes (or after salt/hash changes) you might want to look into my password reset shell script.

Note: this is written for 2.x (so $this->request->data instead of this->data is being used).

 
10 Comments

Posted in CakePHP