Working with passwords in CakePHP

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:

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:

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:

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.

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:

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

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:

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:

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:

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

Steve

February 7, 2012 at 00:39

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 use email in place of username) but to no avail. I also tried passing an array of login credentials to auth-&gt;login but that didn’t work either.

With this ‘pwd’ approach is there any way to follow a successful registration with an auto login?

Mark

February 7, 2012 at 00:48

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.

Steve

February 7, 2012 at 01:54

In your comment – ‘It should be already hashed, though!’ pointed the way. I was attempting the auto login with the unhashed value of ‘pwd’.

but using your behaviour in the DB is another version of hashed password..the users cannot authenticate with the new password ?

Mark

April 28, 2012 at 11:40

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.

Yien

April 28, 2012 at 17:41

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-&gt;data[$this-&gt;alias][‘password’])) {
$this-&gt;data[$this-&gt;alias][‘password’] = AuthComponent::password($this-&gt;data[$this-&gt;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.

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.

Noob

July 3, 2012 at 14:50

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?

Mark

July 4, 2012 at 13:03

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 🙂

Dan Horrocks

July 26, 2012 at 12:48

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

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!

Mark

July 26, 2012 at 14:40

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.

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.

Frank

March 3, 2013 at 13:25

Hi Mark

Thanks for fixing this!

Frank

Frank

March 4, 2013 at 16:27

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

Guilherme

July 3, 2013 at 05:36

yeah, as Frank said, PasswordableBehavior is not working. 🙁

Mark

July 3, 2013 at 10:35

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.

Guilherme

July 3, 2013 at 17:02

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=&gt;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.

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

App::uses('AuthComponent', 'Controller/Component');

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.

Guilherme

July 3, 2013 at 19:19

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 ?

Mark

July 4, 2013 at 00:14

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.

Guilherme

July 4, 2013 at 15:32

Mark, dank!! 🙂

I must say, it woks like a charm!!

Luis

September 26, 2013 at 12:01

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!

msee

November 20, 2013 at 16:05

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-&gt;request-&gt;is(‘post’)) {
$this-&gt;User-&gt;create();
if ($this-&gt;User-&gt;save($this-&gt;request-&gt;data)) {
$this-&gt;request-&gt;data[ ‘User’ ][ ‘password’ ] != $this-&gt;request-&gt;data[ ‘User’ ][ ‘password_Confirmation’ ];
$this-&gt;Session-&gt;setFlash((‘The passwords did not match. Please, try again.’));
return $this-&gt;redirect(array(‘action’ =&gt; ‘add’));
}
else($this-&gt;Session-&gt;setFlash((‘The user has been saved’)));
return $this-&gt;redirect(array(‘action’ =&gt; ‘index’));
}
in this code whether the passwords match or not they evalute as not matching any help here

Mark

November 20, 2013 at 16:13

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.

Dip

June 13, 2016 at 08:47

Forget Password code for cakephp 3.2

Anne

July 12, 2016 at 09:59

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?

Mark

October 3, 2016 at 12:50

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 off by the behavior. Give it a try.