We have to follow certain steps to implement this functionality correctly.
Let’s see in this post how we can implement CakePHP 3 Forgot Password functionality.
First, let’s see what actually includes in CakePHP 3 forgot password functionality.
The scenario, in this case, is, when registered user forgets his/her password and not able to login then using this functionality user can able to change/retrieve his password using his registered email address.
So let’s see first what would be the required steps to implement CakePHP 3 forgot password functionality.
Step 1 – Add “password_reset_token” and “hashval” columns in “users” table and modify your code by baking.
Step 2 – Add “Forgot Password?” hyperlink on Login.ctp. Add “forgotPassword” method in “UsersController”.
Step 3 – Create “forgot_password.ctp” with forgot password form with the email address field, in the desired path. So that “Forgot Password?” hyperlink will redirect to “forgot_password.ctp”.
Step 4 – Submit the forgot password form and verify the registered email address.
Step 5 – Generate password reset token and hash value, set it as user object and save it in “user” table. Generate reset password URL/hyperlink.
Step 6 – Send reset password URL/hyperlink to the email address by using CakePHP 3 email functionality.
Step 7 – Create “resetPassword” function and “reset_password.ctp” in UsersController. And reset the password in this method. Update password reset token and hash value and update users table.
Now let’s see the above steps in more details for CakePHP 3 forgot password functionality.
Step 1:
First of all, in CakePHP 3 forgot password functionality, we need two extra columns in our Users database table.
So add “password_reset_token” and “hashval” columns in “users” table. And modify your code by CakePHP 3 baking process if required.
These columns are required because we need to set password reset code and hash values in our processing.
Step 2:
Now, let’s see the second step in CakePHP 3 forgot password functionality.
When the user wants to login into application he goes to the login page. But because he forgot the password and not able to login he needs a helps to get/change his old password.
So we first add a link on login page which is “Forgot Password?” and hyperlink it “forgotPassword” method.
Assuming, we are using the “UsersController”, adding “forgotPassword” method in this controller.
Go to login.ctp file and in login form add below hyperlink code.
<?php echo $this->Html->link("Forgot Password",['controller'=>'Users','action'=>'forgotPassword']);?>
Now to add “forgotPassword” method in “UsersController“.
public function forgotPassword() { }
Step 3:
Create “forgot_password.ctp” file in src->Template->Users path. Next, add below form in this file having email address field.
When the user clicks on Forgot Password link on login page he will redirect to forgot_password.ctp.
<div class="users form "> <?= $this->Form->create() ?> <fieldset> <legend><?= __('Forgot password') ?></legend> <?= $this->Form->input('email',['label'=>'Enter your registered email address']) ?> </fieldset> <?= $this->Form->button(__('Submit')) ?> <?= $this->Form->end() ?> </div>
Step 4:
When the user enters the email address on forgot_password.ctp and submits the form, it will call “forgotPassword” method and send an email address to this method.
Let’s modify this method and some useful code in it.
First, check whether the request is posted. And collect the submitted email address in $email variable from posted data.
public function forgotPassword() { if ($this->request->is('post')) { if (!empty($this->request->data)) { $email = $this->request->data['email']; } } }
Now check whether the submitted email address is of the registered user, and fetch that user object in $user variable. You can use the below code to do this. And modify the method as:
public function forgotPassword() { if ($this->request->is('post')) { if (!empty($this->request->data)) { $email = $this->request->data['email']; $user = $this->Users->findByEmail($email)->first(); } } }
Check whether $user is not empty using simple if condition.
public function forgotPassword() { if ($this->request->is('post')) { if (!empty($this->request->data)) { $email = $this->request->data['email']; $user = $this->Users->findByEmail($email)->first(); if (!empty($user)) { } } } }
Step 5:
Now as suggested above we need to send email to this user with reset password link and security token. So let’s create a link and password reset token now.
if (!empty($user)) { $password = sha1(Text::uuid()); $password_token = Security::hash($password, 'sha256', true); $hashval = sha1($user->username . rand(O, 100)); $user->password_reset_token = $password_token; $user->hashval = $hashval; }
In the above code, we have first created 40 characters long sha1 string and stored it in $password variable.
Next, we are using Security:hash() function and created 64 characters long password reset token and stored it in a $password_token variable.
We need one more variable i.e. hash value – $hashval variable, which is used in to reset the password reset token again after changing the password.
Yes, I know it seems to be a little bit confusing, but don’t worry once you implement this you will get clear about it.
Set password reset token and hash value variables to the user object.
Now create a URL for reset password form which is having password reset token and hash value using below code:
if (!empty($user)) { $password = sha1(Text::uuid()); $password_token = Security::hash($password, 'sha256', true); $hashval = sha1($user->username . rand(O, 100)); $user->password_reset_token = $password_token; $user->hashval = $hashval; $reset_token_link = Router::url(['controller' => 'Users', 'action' => 'resetPassword'], TRUE) . '/' . $password_token . '#' . $hashval; }
Step 6:
Send this URL in the email to the user’s email address. To know more about sending email in CakePHP 3 please see the post of sending email in CakePHP 3 for details.
if (!empty($user)) { $password = sha1(Text::uuid()); $password_token = Security::hash($password, 'sha256', true); $hashval = sha1($user->username . rand(O, 100)); $user->password_reset_token = $password_token; $user->hashval = $hashval; $reset_token_link = Router::url(['controller' => 'Users', 'action' => 'resetPassword'], TRUE) . '/' . $password_token . '#' . $hashval; $emaildata = [$user->email, $reset_token_link]; $this->getMailer('SendEmail')->send('forgotPasswordEmail', [$emaildata]); }
And update the user data in a database table by saving the user object. And add success flash message.
if (!empty($user)) { $password = sha1(Text::uuid()); $password_token = Security::hash($password, 'sha256', true); $hashval = sha1($user->username . rand(O, 100)); $user->password_reset_token = $password_token; $user->hashval = $hashval; $reset_token_link = Router::url(['controller' => 'Users', 'action' => 'resetPassword'], TRUE) . '/' . $password_token . '#' . $hashval; $emaildata = [$user->email, $reset_token_link]; $this->getMailer('SendEmail')->send('forgotPasswordEmail', [$emaildata]); $this->Users->save($user); $this->Flash->success('Please click on password reset link, sent in your email address to reset password.'); }
The complete forgotPassword action will look like below.
public function forgotPassword() { if ($this->request->is('post')) { if (!empty($this->request->data)) { $email = $this->request->data['email']; $user = $this->Users->findByEmail($email)->first(); if (!empty($user)) { $password = sha1(Text::uuid()); $password_token = Security::hash($password, 'sha256', true); $hashval = sha1($user->username . rand(O, 100)); $user->password_reset_token = $password_token; $user->hashval = $hashval; $reset_token_link = Router::url(['controller' => 'Users', 'action' => 'resetPassword'], TRUE) . '/' . $password_token . '#' . $hashval; $emaildata = [$user->email, $reset_token_link]; $this->getMailer('SendEmail')->send('forgotPasswordEmail', [$emaildata]); $this->Users->save($user); $this->Flash->success('Please click on password reset link, sent in your email address to reset password.'); } else { $this->Flash->error('Sorry! Email address is not available here.'); } } } }
Step 7:
Create resetPassword function in UsersController and add create reset_password.ctp in templates folder.
Add the form in reset_password.ctp as below
<div class="users"> <?= $this->Form->create($user) ?> <fieldset> <legend><?= __('Forgot password') ?></legend> <?= $this->Form->input('new_password',['type'=>'password']) ?> <?= $this->Form->input('confirm_password',['type'=>'password']) ?> </fieldset> <?= $this->Form->button(__('Submit')) ?> <?= $this->Form->end() ?> </div>
Add below code for resetPassword function:
public function resetPassword($token = null) { if (!empty($token)) { $user = $this->Users->findByPasswordResetToken($token)->first(); if ($user) { if (!empty($this->request->data)) { $user = $this->Users->patchEntity($user, [ 'password' => $this->request->data['new_password'], 'new_password' => $this->request->data['new_password'], 'confirm_password' => $this->request->data['confirm_password'] ], ['validate' => 'password'] ); $hashval_new = sha1($user->username . rand(O, 100)); $user->password_reset_token = $hashval_new; if ($this->Users->save($user)) { $this->Flash->success('Your password has been changed successfully'); $emaildata = ['name' => $user->first_name, 'email' => $user->email]; $this->getMailer('SendEmail')->send('changePasswordEmail', [$emaildata]); //Send Email functionality $this->redirect(['action' => 'view']); } else { $this->Flash->error('Error changing password. Please try again!'); } } } else { $this->Flash->error('Sorry your password token has been expired.'); } } else { $this->Flash->error('Error loading password reset.'); } $this->set(compact('user')); $this->set('_serialize', ['user']); }
Now when the user clicks on reset password link or enters reset password URL in the browser, sent in reset password email, he will redirect to reset password page.
Enter the new password and confirm the password and submit the form.
In resetPassword function first, we verify and fetched user related to password reset token and send this user to object to reset password form.
After submitting the form we verify the new and confirm passwords are equal and if they are equal we patch new password in the user object.
To know about the validations done in reset password functions, for new and confirm passwords please see the post CakePHP 3 Change Password functionality with validations.
Next, we create new hash value and generate the new password reset token and set it to the user object’s password_reset_token field.
And save this user in the database.
As we have updated password reset token again, now if the user tries to use that password reset link again he will not able to reset the password again using old URL.
That’s it.
I have implemented password reset functionality using the above procedure.
You can modify and create your won version of functionality also.
Hope you have understood the CakePHP 3 forgot password functionality in this post.
Check whether you are able to run it properly and if any mistake occurs please comment for that. Thanks!.
Subscribe here by email for more tutorials:
how to print the reset_token_link and user email in the view
$emaildata = [$user->email, $reset_token_link];
$this->getMailer('SendEmail')->send('forgotPasswordEmail', [$emaildata]);
Im very confused now because my activation method is working but i cannot properly display the token and it will display the previous key https://stackoverflow.com/questions/48981017/im-getting-the-previous-pass-key-value-instead-of-the-present-one
TemplateEmailhtmlforgot_password_email.ctp
Hi i am getting error like this :
Error: Call to a member function first() on array
File: C:xampphtdocsjobfindsappControllerUsersController.php
Line: 75
what should be password_reset_token and hash value and how and when should these be stored
Hello naresh,
You need to add use CakeUtilitySecurity; above the UsersController class.
Hi,
I am getting error like this Class 'AppControllerSecurity' not found .
>>$hashval_new = sha1($user->username . rand(O, 100));
>>As we have updated password reset token again, now if the user tries to use that password reset link again he will not able to reset the password again using old URL.
And became reseting admin password with 100 random tokens sha1('admin'. rand(O, 100))
Nice!
I didn't understand why you use only 'password_reset_token' to access the URL. Why are you not using the 'hashval' in the URL too to access the 'resetPassword' action? Good tutorial 🙂
Dear,
Also, the password is saving plain text here is my entity code, please help me to fix this issue.
true,
'id' => false
];
protected function _setPassword($value)
{
if(strlen($value)) {
$hasher = new DefaultPasswordHasher();
return $hasher->hash($value);
}
}
}
Hi,
thanks for this example, and i think validation function for ['validate' => 'password'] is missing, please update it too.
Thanks.
Hi,
I am getting flash message that you are not authorized to that location
$this->getMailer('SendEmail')->send('forgotPasswordEmail', [$emaildata]);
plz show me function SendEmail and forgotPasswordEmail code
Hi I'm getting this error plz hekp me
error: Class 'AppControllerRouter' not found
this line: $reset_token_link = Router::url(['controller' => 'Users', 'action' => 'resetPassword'], TRUE) . '/' . $password_token . '#' . $hashval;