RSS
 

Posts Tagged ‘Passwords’

Working with passwords in CakePHP

25 Aug

The basics

Usually, this is already well known. But.. there are still developers who actually store the password unhashed. You always have to store user passwords hashed. You can either use the still very common sha1 method (which is the cake default as for right now) or switch to more secure measures like bcrypt.
If you want to be on the safe side you not only use salts (which is also cake standard) but also a unique salt per user. In case someone actually hacks your DB the information he gets is then usually pretty useless. You can read more about this here.

That also means that the password is a one-way ticket. Due to the technical details on hashs you are not able to recover the password from this hash – hence you always need to display some empty password field which is not prepopulated (see the examples below on how to unset the field from the controller). Most websites also use a confirmation field (second password field below the first one) to make sure, there are no spelling mistakes – since the password fields shood be unreadable using the well-known stars (input type=password).

A clean approach for working with passwords

Most beginners make the mistake to use the raw baked templates and "password" as fieldname for the database field "password" in forms.
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 (except for login, of course).
Always use "pwd" or some other temporary field name for your forms which you will then map to 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;
}

Do not use isset() in this case! It’s bad to accidentally hash an empty string here (if for some reason the field was actually not null). It won’t be noticeable afterwards when the empty string is suddenly a long hash value. So better don’t do that in the first place. !empty() suffices.

Also make sure that you grab and use the password before it gets hashed if you plan on sending an email with the password to the user.
After the hashing there is no way in resolving it back to what it used to be (basic principle of hash algorithms).

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.

For your validation rules you will need a few new methods like validateIdentical (to ensure the password and the confirmation password match) as well some rules on min length etc (more on this and some code in the next section as well as the linked behavior code).

If you use passwords in multiple places (or just use password stuff in multiple apps) it might make sense to separate the code in a clean way. See the next chapter.

Note that in newer versions of CakePHP (>= 2.4) it is recommended to not use the AuthComponent, but the hasher classes directly:

// At the top of the file
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
// Inside your User model
public function beforeSave($options = array()) {
	parent::beforeSave($options);
	if (!empty($this->data[$this->alias]['pwd'])) {
		$PasswordHasher = new SimplePasswordHasher(); // or add array('hashType' => '...') to overwrite default "sha1" type
		$this->data[$this->alias]['password'] = $PasswordHasher->hash($this->data[$this->alias]['pwd']);
	}
	...
	return true;
}

For new apps you should at least consider using Blowfish, though.

Note: The most common mistake with more safe hashing algorithms is to use too short DB fields. Make sure your password field is of at least the length of the hash (for blowfish up to 123). So it is wise to simply use VARCHAR(255) and play it safe. This also leaves room for even "safer" hashes one day.

Introducing my Passwordable behavior

The code can be found on github (CakePHP2.x) [1.3 – careful, different name back then].
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). Especially for beginners this really helps to keep it clean and safe. But I use is everywhere to keep it DRY. In case I have to adjust I usually only have to touch the behavior code.

The most important fact the behavior automatically takes care of is the update process.
If you have the password field in your form and you don’t enter anything it will just skip the password changing part. As soon as you enter something the behavior assumes that you actually want to change it then. Usually this has to be covered in the controller or model at some point. This is also the point most beginners struggle with. The overhead is reduced to the behavior as responsible element.

You can override the default values with Configure::write('Passwordable.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. They are now deprecated, though, as you should be using Configure keys here, as well.

Note: Do NOT add the above beforeSave codesnippet with the manual password hashing as the behavior already does that. Or the password will be hashed twice on save (and thus be rendered unusable).

Register forms

The basic use case first. We just attach the behavior to the model and and the two password fields in the view.
It boils down to:

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable');
	if ($this->User->save($this->request->data, true, array('username', 'email', ..., 'pwd', 'pwd_repeat'))) {
		//SUCCESS flash/redirect
	}
	//ERROR flash
	unset($this->request->data['User']['pwd']);
	unset($this->request->data['User']['pwd_repeat']);
}

The register form then contains

echo $this->Form->input('pwd', array('type'=>'password', 'value'=>'', 'autocomplete'=>'off'));
echo $this->Form->input('pwd_repeat', array('type'=>'password', 'value'=>'', 'autocomplete'=>'off'));

Important: Be careful with your whitelisting (as it can go wrong quickly).
Either use the security component to make sure, only the right fields are passed, processed and saved, or use whitelisting appropriatly (including all form fields that should be saved!). Do NOT use it if you don’t know how.
The declaration of the save method is save($data, $validate, $whitelist) or just save($data, $options) (then including validate and whitelist as array keys in options).
The whitelist MUST contain all relevant fields coming from the controller, so for your registration form all fields including the password fields (as behaviors cannot add fields automatically to the whitelist).

In the above registration form example we add all fields like "username" or "email" – and also "pwd", "pwd_repeat" etc then, of course. The only field the behavior can add automatically, is "password", the final password field name going into the DB. Thus we can omit that here.

Admin Forms

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable', array('require' => false, 'confirm' => false));			
	if ($this->User->save($this->request->data)) {
		//YEAH
	} else {
		//OH NO
	}
}

Using require here is quite important. This makes the validation skip the password fields if you do not enter anything. The password will be left alone.
As admin I also might not want to retype the password. So confirm can possibly be set to false (although a confirmation field is always a good idea).

/users/edit view

Here is the most common scenario of a complete edit form for the user with only basic password input:

if ($this->request->is('post') || $this->request->is('put')) {
	$this->User->Behaviors->attach('Tools.Passwordable', array('require' => false));			
	if ($this->User->save($this->request->data)) {
		//YEAH
	} else {
		//OH NO
	}
}

You have your two input fields in your form again which can be left empty to not update the password. Simple, isn’t it?

On an edit form the user should not have to be forced to change his password. Leaving the field empty will just skip that part. As soon as the user enters a new password, though, validation will get triggered for this field as specified in your settings.

If you use whitelisting (which is a good idea) please make sure, that you set the whitelist to only those fields from your actual model that are allowed to be updated (or use Security component).

Separate password form for the user

Note: This is the form with only the password to be changed – see the other form above for a complete edit view

if ($this->request->is('post') || $this->request->is('put')) {
	// attach the behavior and force the user to enter the current password for security purposes
	$this->User->Behaviors->attach('Tools.Passwordable', array('current'=>true));
	$this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
	if ($this->User->save($this->request->data, true, array('id',  'pwd', 'pwd_repeat'))) {
		//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.
The advantage of such a separate view might be that you can reduce complexity for the user. Especially with confirmation of the current password or more.

Login

This is only for completeness. The login part matches the one in the documentation.
Here we use the field that matches the one in the database – so password in most cases:

echo $this->Form->input('password');

On important issue, though: For the login forms some forget to clear the password after an unsuccessful post.

if ($this->request->is('post') || $this->request->is('put')) {
	if ($this->Auth->login()) {
		...
	}
	// 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 Passwordable 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.

Is there more?

Yes, there is. You can take a look at the behavior itself for more configuration or peek into its test case.
‘allowSame’ might be interesting. false requires the user to use a different password then the one previously entered. This can be useful on a separate "change password" page and if you want to force the user to change it to something than it was before.

Custom auth and hash types

If you derive from the default Form authenticate adapter or the default hashing type, you need to tell the behavior that. See the appropriate settings.
For cake2.4 there is now also the support for "passwordHasher" config.

Your own validation rules

If you do not provide any validation rules for your password fields, the behavior will automatically use the "best practice" ones of his own. But you can always define your own ones in the model to overwrite then (not recommended, though).

Letting users change their password

For users who want to change or have forgotten their password, check out "Tokens" and the reset functionality mentioned there.

I18n

Don’t forget to take the translation strings from the plugin’s Locale folder and translate them accordingly to your language (eng, deu, …) in your APP/Locale folder.

Common pitfalls

If some fields are not saved, make sure your whitelisting is in order or just drop it in favor of the Security component.

If you are creating a password change form and include the id in the form <?php echo $this->Form->input('id'); ?> and also check on it wrongly using is(post) only, it won’t work:

if ($this->request->is('post')) {} // Careful with this

You should always check on both put and post if you don’t know why (or use my quick Common->isPosted()).
Also, it is a good idea to keep the id out of the form and inject it prior to the save call as shown above.

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.

And if you want to create your first (admin) user, you might want to take a look at my User shell which saves you the trouble of manually hashing a password and running some custom sql query. just hit cake Tools.User and "bake yourself one".

Note: this article has been rewritten for 2.x (so $this->request->data instead of this->data is now being used inside the controller scope).

2013-08 Important fix

There has been an important correction to make handling of required input more stable. Please replace any 'allowEmpty' => true with the new 'require' => false.
Attaching the behavior, it will automatically require a password input for BC.

2013-10 Important update

Now, since CakePHP2.5, the whitelisting works as expected even from within behaviors. Only now we can omit all form fields that the behavior works with (pwd, pwd_repeat and pwd_current) in the whitelisting. The behavior will add them automatically, if needed.
For a password change form then, for example, it boils down to just the id now:

if ($this->User->save($this->request->data, array('fieldList' => array('id')))) {}

2014-04-02 Use Blowfish now if you can

If you are developing a new 2.x app, use the new 3.x standard right away. It is way safer and useful to use Blowfish or any of the "real" password hashing algos.
With the Passwordable behavior it is this config setting:

$config['Passwordable'] = array(
	'authType' => 'Blowfish'
);

All you then need to do is to adjust your AppController:

public function beforeFilter() {
	$this->Auth->authenticate = array(
		'Form' => array(
			'passwordHasher' => Configure::read('Passwordable.authType'))); // Or simply `Blowfish`
	parent::beforeFilter();
}

To migrate live applications with existing users and password this is a little bit more tricky.
There is an open ticket regarding that issue – and some MultiHasher class will probably serve best.
If would first try to match the new algo and if not possible will try the deprecated algos and maybe even rehash it to the new one if possible. Sooner or later all
active accounts should have a up-to-date blowfish password.

2014-07-08 Backported PHP5.5+ password_hash()

The new PHP5.5+ password_hash()/password_verify()/password_needs_rehash() functions are now available in 2.x via Shim.Modern PasswordHasher class. I backported the 3.x DefaultPasswordHasher class for this.
All that is needed, is to add this new hasher to the Auth and Passwordable configs:

'authType' => 'Blowfish',
'passwordHasher' => 'Shim.Modern'

See the test cases for details.

You can also pass in options (like "cost"):

'passwordHasher' => array('className' => 'Shim.Modern', 'cost' => 20)

And don’t forget to add the password_compat shim via composer or the included version of it via require if you are not yet on PHP5.5, but PHP5.4 etc:

// Without composer - in your bootstrap.php
require CakePlugin::path('Shim') . 'Lib/Bootstrap/Password.php';
// With composer
"require": {
	"ircmaxell/password-compat": "dev-master"
}

To complete the new "modern" password hashing, you can also make login() automatically rehash the passwords if needed:

// In your login action
if ($this->Auth->login()) {
	$this->Common->flashMessage(__('You have logged in successfully.'), 'success');
	
	$this->User->Behaviors->load('Tools.Passwordable', array('confirm' => false));
	$password = $this->request->data['User']['password'];
	$dbPassword = $this->User->field('password', array('id' => $user['id']));
	if ($this->User->needsPasswordRehash($dbPassword)) {
		$data = array(
			'id' => $user['id'],
			'pwd' => $password,
			'modified' => false
		);
		if (!$this->User->save($data)) {
			trigger_error('Could not store new pwd for user ' . $user['id'] . '.');
		}
	}
	return $this->redirect($this->Auth->redirectUrl());
}

Note how it uses the currently stored hash with needsPasswordRehash() to determine if an update is necessary. If so it will do that automatically.
The additional check on save() might not be necessary – but in case you changed your validation params you might need to loosen the validation here then in order for the password to be rehashed without errors.

In future versions (CakePHP3.x) this will be easier as the Auth component will directly provide wrapper methods for this ($this->Auth->loginProvider()->needsPasswordRehash()).
But until then this shim works quite well in 2.x.

For the code visit the Shim plugin.

CakePHP 3.x

For CakePHP 3.x it is still quite convenient to use the behavior, even for login and rehashing. See the 3.x compatible version of the Tools plugin .
The docs for it can directly be found there in Tools Plugin 3.0 documentation

 
36 Comments

Posted in CakePHP

 

Storing and managing accounts

03 Jun

I get questions like "What is the svn address again" or "What was my password" all the time.
You could write them all down somewhere on paper. But copy-and-pasting them is so much easier and faster.
So we end up putting them on the hard drive.

Use encrypted storage files

With truecrypt (www.truecrypt.org/), for example, you can create a container file that includes itself in the system as an own partition (with any letter you chose like Z:/). But any other software will do, as well.

There you can safely store all the passwords, account infos etc in plain files at one single location. easy to find, easy to update.

 
No Comments

Posted in Common