It’s crucial to ensure the User account’s security when implementing a forgotten password functionality in Magento 2. One aspect of this is preventing users from reusing old passwords. Custom validation needs to be added to the password reset process. 

This blog post will guide you through the steps to implement custom validation for the Forgotten Password feature in Magento 2, enhancing the overall security of your online store.

Steps to Add Custom Validation for Forgotten Password in Magento 2

We have implemented a customer reset password validation feature. When a customer goes to the forgot password page and attempts to enter the same password, we verify whether the customer has previously used this password in the admin panel.

Before implementing the custom validation, it’s important to identify the current password and understand the mechanism used to store and retrieve the user’s password. Magento 2 uses hashing algorithms to store passwords securely.

Create a Custom Validation Module Develop a custom module in Magento 2 to handle the password reset process. This module should include functionality for checking whether the new password matches any of the user’s previous passwords.

Recommended Read: How To Verify the Customer’s Passwords on the Edit Page in Magento 2?

To create a custom validation for Forgotten Password follow the below steps:

Step 1: Create a “registration.php”

app/code/Dckap/ForGotValidate/registration.php

<?php
/**
* @package    Dckap_ForGotValidate
* @copyright  Copyright (c) 2023 DCKAP Inc (http://www.dckap.com)
* @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*/
\Magento\Framework\Component\ComponentRegistrar::register(
   \Magento\Framework\Component\ComponentRegistrar::MODULE,
   'Dckap_ForGotValidate',
   __DIR__
);

Step 2: Create a “Module.xml” file

The following path has: 

app/code/Dckap/EventsObservers/etc/module.xml

<?xml version="1.0"?>
<!-- /**
* @package    Dckap_ForGotValidate
* @copyright  Copyright (c) 2023 DCKAP Inc (http://www.dckap.com)
* @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*/ -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
   <module name="Dckap_ForGotValidate" setup_version="1.0.0">
       <sequence>
           <module name="Magento_Customer" />
       </sequence>
   </module>
</config>

Step 3: Create a “routes.xml” file 

The following path has: 

app/code/Dckap/ForGotValidate/etc/frontend/routes.xml

<?xml version="1.0"?>
<!-- /**
* @package    Dckap_ForGotValidate
* @copyright  Copyright (c) 2023 DCKAP Inc (http://www.dckap.com)
* @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*/ -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
   <router id="standard">
       <route id="forgotvalidate" frontName="forgotvalidate">
           <module name="Dckap_ForGotValidate" />
       </route>
   </router>
</config>

Step 4: Now, create one more file “Customerhash.php” 

With that create a file path that has the following: 

app/code/Dckap/ForGotValidate/Controller/Index/Customerhash.php

<?php


namespace Dckap\ForGotValidate\Controller\Index;


use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Customer\Model\Customer;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Customer\Model\CustomerRegistry;


class Customerhash extends Action
{
   /**
    * @var CustomerRepositoryInterface
    */
   protected $customerRepository;
   /**
    * @var JsonFactory
    */
   protected $jsonResultFactory;
   /**
    * @var Customer
    */
   protected $customerModel;
   /**
    * @var EncryptorInterface
    */
   protected $encryptor;
   /**
    * @var CustomerRegistry
    */
   protected $customerRegistry;


   /**
    * Customerhash constructor.
    * @param Context $context
    * @param CustomerRepositoryInterface $customerRepository
    * @param Customer $customerModel
    * @param JsonFactory $jsonResultFactory
    * @param EncryptorInterface $encryptor
    * @param CustomerRegistry $customerRegistry
    */


   public function __construct(
       Context $context,
       CustomerRepositoryInterface $customerRepository,
       Customer $customerModel,
       JsonFactory $jsonResultFactory,
       EncryptorInterface $encryptor,
       CustomerRegistry $customerRegistry,


   ) {


       parent::__construct($context);
       $this->customerRepository = $customerRepository;
       $this->jsonResultFactory = $jsonResultFactory;
       $this->customerModel = $customerModel;
       $this->encryptor = $encryptor;
       $this->customerRegistry = $customerRegistry;
   }


   /**
    * @return mixed
    */


   public function execute()
   {
       $customerId = $this->getRequest()->getPost('customer_id');
       $newPassword = $this->getRequest()->getPost('new_password');
       $temp = $this->isPasswordAlreadyUsedForCustomer($customerId,$newPassword);
       $customerModel = $this->customerModel->load($customerId);


       $customerData = [
       'id' => $customerModel->getId(),
       'email' => $customerModel->getEmail(),
       'password_match' => $temp,
       ];


       $result = $this->jsonResultFactory->create();
       return $result->setData($customerData);
   }


   /**
    * @param $customerId
    * @param $password
    * @return bool
    */
   public function isPasswordAlreadyUsedForCustomer($customerId, $password)
   {
       $customerSecure = $this->customerRegistry->retrieveSecureData($customerId);
       $hash = $customerSecure->getPasswordHash();
       if ($this->encryptor->validateHash($password, $hash) == true) {
           return true;


       }
       return false;
   }
}

Step 5: Create one more file “customer_account_createpassword.xml” 

Add a file path that has the following:

app/code/Dckap/ForGotValidate/view/frontend/layout/customer_account_createpassword.xml

<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
   <head>
       <title>Set a New Password</title>
   </head>
   <body>
       <referenceBlock name="root">
           <action method="setHeaderTitle">
               <argument translate="true" name="title" xsi:type="string">Set a New Password</argument>
           </action>
       </referenceBlock>
       <referenceContainer name="content">
           <block class="Magento\Customer\Block\Account\Resetpassword" name="resetPassword" template="Dckap_ForGotValidate::form/resetforgottenpassword.phtml" cacheable="false"/>
       </referenceContainer>
   </body>
</page>

Step 6: Create one more file “resetforgottenpassword.phtml”

Add a file path that has the following:

We need to override this page from the vendor

app/code/Dckap/ForGotValidate/view/frontend/templates/form/resetforgottenpassword.phtml

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/


/** @var \Magento\Customer\Block\Account\Resetpassword $block */
?>
<form action="<?= $block->escapeUrl($block->getUrl('*/*/resetpasswordpost', ['_query' => ['id' =>$block->getRpCustomerId(),'token' => $block->getResetPasswordLinkToken()]])) ?>"
     method="post"
       <?php if ($block->isAutocompleteDisabled()) : ?> autocomplete="off"<?php endif; ?>
     id="form-validate"
     class="form password reset"
     data-mage-init='{"validation":{}}'>
     <div id="form-validate-customerId" value = <?php echo $block->getRpCustomerId();?> >
           <input type="hidden" name="customer_id" value="<?php echo $block->getRpCustomerId(); ?>">
     </div> 
   <fieldset class="fieldset" data-hasrequired="<?= $block->escapeHtmlAttr(__('* Required Fields')) ?>">
       <div class="field password required" data-mage-init='{"passwordStrengthIndicator": {}}'>
           <label class="label" for="password"><span><?= $block->escapeHtml(__('New Password')) ?></span></label>
           <div class="control">
               <input type="password" class="input-text" name="password" id="password"
                      data-password-min-length="<?= $block->escapeHtmlAttr($block->getMinimumPasswordLength()) ?>"
                      data-password-min-character-sets="<?= $block->escapeHtmlAttr($block->getRequiredCharacterClassesNumber()) ?>"
                      data-validate="{required:true, 'validate-customer-password':true}"
                      autocomplete="off">
               <div id ='password-strength-meter'></div>      
               <div id="password-strength-meter-container" data-role="password-strength-meter" aria-live="polite">
                   <div id="password-strength-meter" class="password-strength-meter">
                       <?= $block->escapeHtml(__('Password Strength')) ?>:
                       <span id="password-strength-meter-label" data-role="password-strength-meter-label">
                           <?= $block->escapeHtml(__('No Password')) ?>
                       </span>
                   </div>
               </div>
           </div>
       </div>
       <div class="field confirmation required">
           <label class="label" for="password-confirmation"><span><?= $block->escapeHtml(__('Confirm New Password')) ?></span></label>
           <div class="control">
               <input type="password" class="input-text" name="password_confirmation" id="password-confirmation" data-validate="{required:true,equalTo:'#password'}" autocomplete="off">
           </div>
       </div>
   </fieldset>
   <div class="actions-toolbar">
       <div class="primary">
           <button type="submit" id ="submit-password" class="action submit primary"><span><?= $block->escapeHtml(__('Set a New Password')) ?></span></button>
       </div>
   </div>
</form>


<script>


require(['jquery'], function ($) {
   $(document).ready(function () {
       var customerId = $('input[name="customer_id"]').val();
       var newPasswordTimeout; 


       var response;


       $('input[name="password"]').on('input', function () {
           clearTimeout(newPasswordTimeout);
           newPasswordTimeout = setTimeout(function () {
               var newPassword = $('input[name="password"]').val();
               console.log(newPassword);
               $.ajax({
                   dataType: 'json',
                   method: 'POST',
                   contentType: 'application/x-www-form-urlencoded',
                   url: '<?php echo $block->getUrl('forgotvalidate/index/customerhash');?>',
                   showLoader: true,
                   crossDomain: false,
                   data: {
                       customer_id: customerId,
                       new_password: newPassword
                   },
                   success: function (ajaxResponse) {
                       response = ajaxResponse;
                       if (response.password_match == true) {
                           $('#password-strength-meter').html("<div class='message-error-password'>Password already used try for new password.</div>").css('color', 'red');
                       } else {
                           $('#password-strength-meter').html('');
                       }
                   },
                   error: function (error) {
                       console.error('Error:', error);
                   }
               });
           }, 1000);   
       });


       $('#submit-password').on('click', function (event) {
           event.preventDefault();


           if (response && response.password_match == true) {
               $('#password-strength-meter').html("<div class='message-error-password'>Password already used try for new password.</div>").css('color', 'red');
           } else {
               $('#password-strength-meter').html('');
               $(this).closest('form').submit();
           }
       });
   });
});


</script>

Read Also: Steps to Create a Custom Price For a Product in Magento 2

Module Usage 

Retrieve User’s Password History

we need to Implement a method to retrieve the user’s password history from the database. This history will be used to compare against the new password during the validation process.

Compare New Password with History

During the password reset process, compare the new password provided by the user with their password history. If the new password matches any previous one, reject it and prompt the user to choose a different one.

Display an Error Message

It should be done like the current password is already used for this customer and If the new password is rejected due to being a reused password, display an appropriate error message to inform the user of the requirement to choose a unique password.

Complete Password Reset 

Once the user selects a new password that meets the validation criteria, proceed with the password reset process as usual.

Module Output

Implement Custom Validation for the Forgotten Password Feature with Klizer Experts

Users are prevented from reusing old passwords by implementing custom validation for the Forgotten Password functionality in Magento 2, thereby enhancing the security of their accounts. 

This extra layer of validation ensures that compromised passwords cannot be reused, reducing the risk of unauthorized access to user accounts. With proper implementation and error handling, the password reset process remains user-friendly while maintaining a high level of security.

Get in touch with us to get further inquiries and explore how our solutions can benefit your business.

Rathina Pranesh C

Rathina Pranesh C is a Software Engineer at Klizer, specializing in Magento ecommerce solutions. With over 1.5 years of hands-on experience in the Magento platform, Rathina is dedicated to creating seamless online shopping experiences. His passion for coding and commitment to excellence drives them to continuously innovate and deliver top-notch solutions to enhance the field of ecommerce.

Get in Touch

Get Expert Advice

At Klizer, we bring over 18 years of specialized expertise in ecommerce website development and design.

© Copyright 2024 Klizer. All Rights Reserved