1

I'm trying to reset a user password programmatically in Drupal 8 without the email link. For this the first step would be to check if the password(which is plain) that the user enters in the password reset form is matching with the hashed password of the respective user. And then save the new password.

Each time I hash the password, its giving a different value, this is because its salted. How shall I compare the password which the user enters in the form with the hashed password from the table.

This is the code that I have in my controller for checking the current password :

<?php
/**
* @file
* contains \Drupal\customer_profile\Controller\ProfileUpdateController
*/

 namespace Drupal\customer_profile\Controller;

 use Drupal\Core\Controller\ControllerBase;

 use Symfony\Component\HttpFoundation\JsonResponse;
 use Drupal\Core\Password\PhpassHashedPassword;
 use Drupal\Core\Password\PasswordInterface;

 class ProfileUpdateController extends ControllerBase {

   $user =\Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
   $ret = \Drupal\Core\Password\PasswordInterface::check('secret', $user);
   //$user->setPassword('secret');
   //$pass = $user->save();


   $response->password = $ret;
   return new JsonResponse($response);
  }
 }

After using \Drupal\Core\Password\PasswordInterface::check() method, for comparing the plain text password with the hash password, I get this fatal error :

Fatal error: Non-static method Drupal\Core\Password\PasswordInterface::check() cannot be called statically, assuming $this from incompatible context in C:\xampp\htdocs\ijarah\modules\custom\customer_profile\src\Controller\ProfileUpdateController.php on line 725

In Drupal 7 we have user_check_password to check the password with string and user_hash_password for hashing. How can I approach the same in Drupal 8.

Please help.

2

The hash is different every time, that's by design, because it is salted.

If you want to change the password of a user, you don't have to hash it yourself. Just load the user, set the password and save.

$user = User::load(1);
$user->setPassword('secret');
$user->save();

For the fatal error that you mentioned before, @mradcliffe already had the answer in a comment of this (now deleted) answer:

I think MigratePassword is overriding the default password service. You can try changing the constructor argument to be PasswordInterface instead of PhpassHashedPassword if the Drupal instance is using MigratePassword. Or disable migrate module.

Always type hint on the interface if an interface is available. Services might be switched out, like migrate is doing that for example.

To check the password, you just have to use the other method of that interface, \Drupal\Core\Password\PasswordInterface::check(). Those two methods are a direct match for user_hash_password() and user_check_password()

  • Thanks for the response @Berdir, actually the reason I'm trying to hash the password is to check if the current password that the user gives in the password reset form matches with the hashed password in the table. But each time I do a hashing the value is different as you have said, its salted. Once the match is success I can proceed with new password save. – wilNev Mar 23 '16 at 21:40
  • I deleted my answer because the question is "how do I check if the current password that the user gives in the password reset form matches with the hashed password?". – mradcliffe Mar 23 '16 at 21:43
  • The question is edited. Hope this is more clear. – wilNev Apr 3 '16 at 7:45
  • @mradcliffe I would actually recommend to undelete your answer. It's pretty close, you just have to switch hash() to check(). – Berdir Apr 3 '16 at 8:48
  • 1
    Yes, the error means that the calling code is not doing so properly. The :: is meant for static methods. In order to use the check method, the calling code needs to call the check method on an instance of an object that implements PasswordInterface. Learning object-oriented programming in PHP is a bit out of scope for DA though. – mradcliffe Apr 10 '16 at 14:40
1

Ok, the solution would be to use the Dependency Injection as suggested by @mradcliffe above and then use the check() method. Here is the code :

<?php    
/**
* @file
* contains \Drupal\customer_profile\Controller\ProfileUpdateController
*/
namespace Drupal\customer_profile\Controller;

use Drupal\Core\Controller\ControllerBase;

use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface; 
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Password\PasswordInterface;

 class ProfileUpdateController extends ControllerBase implements ContainerInjectionInterface {

  public function __construct(PasswordInterface $password_hasher, AccountInterface $account) {
   $this->passwordHasher = $password_hasher;
   $this->account = $account;
  }

   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('password'),
       $container->get('current_user')
     );
   }

   public function updatePassword() {
     //global $user;
     $response = new \stdClass();
      //check the plain password with the hashed password from db
     $pass = $this->passwordHasher->check('secret', 'hashed_password_from_db');

     $response->password = $pass;
     // this will return true if the password matches or false vice-versa
     return new JsonResponse($response);
   }
  }
   ?>

After checking the password, we can save the new password using

  $user = User::load(1);
  $user->setPassword('new_password_secret');
  $user->save();

Hope this helps others :)

1

Note that this answer is superceded by Berdir's answer. I initially removed it because it no longer answers the question, and did not want to confuse anyone. It is undeleted to use as an example of using dependency injection on a service and the hash method.

The password service suggests that the argument for the constructor should be 16. Additionally the minimum value is 7 and the maximum value is 30. Anything outside of those ranges will return FALSE (on failure), which your code should really be checking to be secure.

In the following example, you would not need to worry about the constructor arguments. In this way your code is more portable if password hashing changes or someone installs a module that changes the password service.

class ProfileUpdateController extends ControllerBase implements ContainerInjectionInterface {

   public function __construct(PasswordInterface $password_hasher, AccountProxy $account) {
     $this->passwordHasher = $password_hasher;
     $this->account = $account;
   }

   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('password'),
       $container->get('current_user')
     );
   }

   public function updatePassword() {
     $response = new \stdClass();
     $pass = $this->passwordHasher->hash('1234');
     if (!$password) {
        throw new \Exception('An error occurred.');
     }
     $response->password = $pass;
     return new JsonResponse($response);
   }
 }

Subjective comment: Drupal core does not return passwords as part of user objects in its Restful API because it does not consider that a secure approach.

  • Thanks for the response @mradcliffe, I've modified the code. This time getting a fatal error. – wilNev Mar 23 '16 at 12:51
  • I think MigratePassword is overriding the default password service. You can try changing the constructor argument to be PasswordInterface instead of PhpassHashedPassword if the Drupal instance is using MigratePassword. Or disable migrate module. – mradcliffe Mar 23 '16 at 18:23
  • Ok, I finally found the solution. I tried using the AccountInterface as the second parameter in the constuctor. Using AccountProxy gave me a fatal error. And then used the check() instead of hash() . Will post the code below. Thanks for your help @mradcliffe :) – wilNev Apr 10 '16 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.