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 should 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 field name 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 = []) {
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 accidentally 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 = []) {
parent::beforeSave($options);
if (!empty($this->data[$this->alias]['pwd'])) {
$PasswordHasher = new SimplePasswordHasher(); // or add ['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 redundantly 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 accidentally 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 code snippet 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 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, ['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', ['type' => 'password', 'value' => '', 'autocomplete' => 'off']);
echo $this->Form->input('pwd_repeat', ['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 appropriately (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', ['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).
Here you definitely want to add 'autocomplete' => 'new-password'
to make sure the browser doesn’t auto-fill the field and you accidentally set a new password here.
/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', ['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', ['current' => true]);
$this->request->data['User']['id'] = $this->Session->read('Auth.User.id');
if ($this->User->save($this->request->data, true, ['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 field name 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 than 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 else 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, ['fieldList' => ['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'] = [
'authType' => 'Blowfish'
];
All you then need to do is to adjust your AppController:
public function beforeFilter() {
$this->Auth->authenticate = [
'Form' => [
'passwordHasher' => Configure::read('Passwordable.authType'),
]
]; // Or simply `Blowfish`
parent::beforeFilter();
}
To migrate live applications with existing users and passwords 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' => ['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', ['confirm' => false]);
$password = $this->request->data['User']['password'];
$dbPassword = $this->User->field('password', ['id' => $user['id']]);
if ($this->User->needsPasswordRehash($dbPassword)) {
$data = [
'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 in Tools Plugin 3.0 documentation
I’m trying to use the behavior with a registration form and have a problem. If the entered pwd is shorter than the minimum length I get the following:
Notice (8): Undefined offset: 2 in [C:\wamp\www\cake_1_3\cake\libs\view\helpers\form.php, line 487
Things work as expected for valid password lengths, and when the pwd_repeat doesn’t match.
I’m using cakephp 1.3.
Any ideas what would cause this?
Maybe it has to do with the validation rules?
I only use the 2.x version nowadays. But it must be sth related to this.
The problem turned out to be with the array used in the behavior’s validation message parameters.
When I change the following:
‘message’ => array(‘valErrBetweenCharacters %s %s’, PWD_MIN_LENGTH, PWD_MAX_LENGTH),
to:
‘message’ => ‘some message…’,
things worked fine.
Any examples of how I would use the behavior in a lost password scenario?
oh, thats possible
This has been introduced in cake2.x I think. But since 1.3 I used my own custom invalidate() method which can also handle it quite well (AppModel):
For "lost password scenarios" you first need to find the record to the given email/username and send an activation email with a link to a page where he can enter a new password.
there it will be the same again as above: pwd + pwd_repeat must match.
That’s all.
One other problem. Now that I’ve replaced the ‘password’ field with the ‘pwd’ field in my registration form the auto login after successful registration no longer works.
I do the following (if the User->save is successful):
$this->Auth->login($this->data);
Since a ‘password’ field is no longer on the form this doesn’t work.
I’ve tried inserting the following before the Auth->login:
$this->Auth->fields = array(
‘username’ => ’email’,
‘password’ => ‘pwd’
);
(I use email in place of username) but to no avail. I also tried passing an array of login credentials to auth->login but that didn’t work either.
With this ‘pwd’ approach is there any way to follow a successful registration with an auto login?
My guess:
Either use "password" instead "pwd" for the field (you can pass on this setting to the behavior)
or use some beforeValidate/beforeSave() method in the model to copy the pwd field content to password.
It should be already hashed, though!
By the way:
in cake2 you can always call Auth->login() manually. Not sure how to get around that in 1.3.
In your comment – ‘It should be already hashed, though!’ pointed the way. I was attempting the auto login with the unhashed value of ‘pwd’.
So now if the User save is successful I do the following:
And it works!
Thanks for the help (and the behavior)
Hello, I am using your change password behaviour
Why is the Security::hash method return 2 different kind of output.
ie:
I use these line
$text = Security::hash(‘arandompwd’, null, true);
echo $text;
will output a correct hashed password.
but using your behaviour in the DB is another version of hashed password..the users cannot authenticate with the new password ?
Yien, if you don’t change the defaults, the behavior will use the exact same method (with the same arguments). therefore the password should be correctly hashed.
where are you using your own hash method – at what part of the code? also – did you change anything there in your core? because the auth component then might use those different settings for the login mechanism and will therefore fail.
Dear Mark,
Thanks for your reply. After a few tests, found out that password is hashed twice.
I have a beforeSave() function in User Model, which is following the default Cake 2.0 ACL tutorial.
—
public function beforeSave() {
if (isset($this->data[$this->alias][‘password’])) {
$this->data[$this->alias][‘password’] = AuthComponent::password($this->data[$this->alias][‘password’]);
}
return true;
}
—
If follow your naming practice(using login form, changing the ‘password’ field to custom name), then set the beforeSave() to detect it(like your tut above), the auth won’t login user.
The beforeSave() function I use in #1, will again hash the password, along with your plugin changepwd behaviour used.
I had to use this line in beforeSave()
if(!empty($this->data[‘User’][‘password’]) && !isset($this->data[‘User’][‘id’]))
So that ‘ChangePassword’ action is not counted in, only new register or add user manually will trigger this auto hash.
I then use a manual Security::hash method in the "forgot password" action, when a new passwd is generated randomly.
This is definitely not the best solution, I have to live with it at the moment.
I renamed the field in the form from ‘password’ to ‘pwd’. I used your beforeSave() -code, but it doesn’t work.
Should I rename the field in the DB as well?
you are not supposed to use the before save with the behavior. as you can see above the behavior is introduced after and replaces this altogether 🙂
Hey,
I’m trying to use your behavior and it’s working well, I’ve just got one problem but I’m unsure if it’s intended or if I’m doing something wrong!
When I’m editing a user, I’ve set allowEmpty to true so they can leave the pwd field blank if they want to keep the current password, this works fine.
But when I change a user’s password the validation on the length is no longer applied, I can see the code which does this but can’t seem to work out how to make it work in the way I described. (Must be between x and y only when there’s not a blank password!)
I really should clarify this – let me rewrite the tutorial later on to make it clearer. also I have to recheck if it works with the latest version 2.3 of cake.
Ah I see, thanks for the heads up there, could have been rather embarrassing later down the line.
I seem to be having problems though, For example I’m editing a user and have placed your behaviour into my app and am using the code:
{code type=php}if ($this->request->is(‘post’) || $this->request->is(‘put’)) {
$this->User->Behaviors->attach(‘ChangePassword’);
…
{/php}
This works fine when entering a new password for the user (Between 8 and 16 chars and confirms with pwd_repeat)
But doesn’t seem to work when I leave the password field blank so to not change the user’s password, it still pops up saying the password should be between 8 and 16 chars.
Many thanks for your help, I’m just pretty stumped!
I updated the above tutorial. The issue was mainly with
allowEmpty
=>true.If you don’t set this to true the user will be required to enter his credentials. This is the default use case for registration and password changing sites.
But for users edit views you expect it to skip the field if you do not enter any new password.
Therefore you need to set
allowEmpty
to true in this case telling the behavior that we do not necessarily require a password change here.What you expected should then be covered without any additional configuration, by the way. It automatically leaves the password field in the database alone as long as you don’t enter anything in the input field.
Hi Mark
If use "current" / "pwd_current" I get this error message:
I use this behaviour standalone (without tools).
Can you help?
Thanks,
Frank
Using it standlone is not recommended. But nevertheless it should still work in this case 🙂
Indeed, without setting "auth" to "Auth" manually in the settings it would run into this issue. I fixed it. Check out the master branch.
And thank you for reporting it.
Hi Mark
Thanks for fixing this!
Frank
Hi Mark
I’ve replaced the previous version with your modified one from the master branch. It seems the behaviour is completly broken. Validation of the password fields is skipped. The value from the password-field will be directly saved. Ive not looked any deeper yet – perhaps you know already why this happens… 😉
Thanks,
Frank
yeah, as Frank said, PasswordableBehavior is not working. 🙁
You need to be more specific. Do you use it as intended and explained above? Do you get any error? What is your code etc?
It is working for me just fine in all my apps.
Mark, what I’m trying to say is, validation of the current password fields is skipped, however the comparative between the two new fields and min and max lengths check works fine, I can’t understand why but, even though "current=>true" the validateCurrentPwd is never called. I’m working with cakephp 2.3.6
Thanks in advance, you realy did an amazing plugin it’s only about this issue.
Any details? What your form field code looks like etc?
Mark, please.. have a look:
http://pastebin.com/5F2UULmH
May I forgot to include field pwd_current in save UsersController? like:
.
if I do it, I get the error:
No Authentication class found (MyAuthComponent)
🙁
Do you spot your mistake?
You cannot just whitelist the fields without at least allowing the "pwd_current" field to be passed on, as well.
What Auth class are you using? The normal AuthComponent?
You might need to App::uses() the class prior to calling save():
I never had to do that since it will always have been triggered in my code prior to using the behavior. But I don’t know your exact setup.
yah, thks Mark.
What I’m doing different is using Blowfish to hash the passwords, but how to use it with AuthComponent function identify inside validateCurrentPwd function??
There is another function or any clue ?
It didnt support other auth or hash types.
I modified it to support both Blowfish and different hashing algorithms. I also added support for the new 2.4 passwordHasher option.
Let me know how things work out. The tests look promising so far.
Mark, dank!! 🙂
I must say, it woks like a charm!!
I am having some problems with your stuff.
When I introduce a password (and password confirm) on a edit form, the password is changed, but some rules (like minlength-maxlength ) are not working. At fact, it lets introduce new passwords with 3 characters (having a minlength of 8)
Would you help me? Thanks!
hi I am trying to do validation on my action(in the Controller) instead of doing it in the model.Its in the match passwords add action section.
public function add() {
if ($this->request->is(‘post’)) {
$this->User->create();
if ($this->User->save($this->request->data)) {
$this->request->data[ ‘User’ ][ ‘password’ ] != $this->request->data[ ‘User’ ][ ‘password_Confirmation’ ];
$this->Session->setFlash((‘The passwords did not match. Please, try again.’));
return $this->redirect(array(‘action’ => ‘add’));
}
else($this->Session->setFlash((‘The user has been saved’)));
return $this->redirect(array(‘action’ => ‘index’));
}
in this code whether the passwords match or not they evalute as not matching any help here
That’s not how it’s done.
The controller should only invoke the validation.
Either use my behavior or code it as suggested above or the official documentation. Don’t go down any wild paths.
Forget Password code for cakephp 3.2
Hey Mark,
I have a problem, maybe you have an idea what it might be: I implemented a password reset function (working with Cake2), everything seems to be working, the new password gets passed correctly to the beforeSave-function (that looks pretty much identical to your example) and the database value gets updated. But when trying to login with the new password, the AuthComponent login doesn’t seem to be working. I didn’t change anything that might influence that functionality, I really only update the password field in the database, so I have no idea why the Auth method isn’t working. Do you have any advice for me?
Anne, using the Passwordable behavior the advantage is you don’t have to mess with beforeSave or anything in the model directly anymore. It will all be taken care of by the behavior. Give it a try.