Home > Net >  symfony 5 new Authenticator-based Security - createHasher() must be of the type array, object given
symfony 5 new Authenticator-based Security - createHasher() must be of the type array, object given

Time:10-13

I try to migrate my symfony old application to the newest authentication system using a custom hasher.

I'm facing with the following issue

Argument 1 passed to Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory::createHasher() must be of the type array, object given, called in /var/apache/www/celian_sf/vendor/symfony/password-hasher/Hasher/PasswordHasherFactory.php on line 112

The error occurs when form login is submitted and ´ authenticate method when returning passport object

Executing bin/console security:hash-password throws exactly same error

Using the standard algorithm auto for the hasher config the error goes away but I entered into an infinite loop too many redirect

To verify encoded password I need to get and user instance to retrieve the salt which always different.

I know SHA1 is not strong enough that's no the point this is a legacy application

Why this error occurs? What’s wrong with my implementation?

What's currently working:

return new Passport(
      new UserBadge($login),
      new PasswordCredentials(
        
        function ($credentials, UserInterface $user) {
          return sha1($credentials . '||' . $user->getSalt()) === $user->getPassword();
        },
        $passw
      )
    );

I would like to hash password into my custom hasher.

security.yaml

security:
    enable_authenticator_manager: true

    providers:
        # nom du provider
        myprovider:
            # type de provider
            entity:
                class: App\Entity\Utilisateur
                property: loginUtil

    password_hashers:
        App\Entity\Utilisateur:
            id: App\Security\ShaOneEncoder
    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            anonymous: false
            provider: myprovider
         
            user_checker: App\Security\UserChecker
        
            access_denied_handler: App\Security\AccessDeniedHandler
            
            custom_authenticators:
                - App\Security\LoginFormAuthenticator
            logout:
                path: logout

LoginFormAuthenticator.php

<?php

namespace App\Security;

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{

  private $params;

  public function __construct(ParameterBagInterface $params)
  {
    $this->params = $params;
  }

  /**
   * supports() - is called on every request
   *
   * @param Request $request
   * @return bool
   */
  public function supports(Request $request): bool
  {
    if (
        ($request->attributes->get('_route') === 'login_' . $request->getLocale()
          && $request->isMethod('POST'))
    ) {
        return true;
    }
  
    return false;
  }

  public function authenticate(Request $request): PassportInterface
  {
    // By default credentials are those of visiteur's account
    $login = $this->params->get('standard.login_visiteur');
    $passw = $this->params->get('standard.passwd_visiteur');

    // If user submitted form, get credentials from user's inputs
    if ($request->attributes->get('_route') === 'login_' . $request->getLocale() && $request->isMethod('POST')) {
      $login = $request->request->get('login');
      $passw = $request->request->get('passw');
    }

    return new Passport(new UserBadge($login), new PasswordCredentials($passw));
  }

  /**
   * Perform operations when authentication is success
   *
   * @param Request $request
   * @param TokenInterface $token
   * @param string $providerKey le nom du firewall derriere lequel l'utilisateur est authentifie
   * @return void
   */
  public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  {
    // success
  }

  /**
   * Perform operations when authentication is failure
   *
   * @param Request $request
   * @param AuthenticationException $exception
   * @return void
   */
  public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
  {
    // failure
  }

  protected function getLoginUrl(Request $request): string
  {
    // TODO: Implement getLoginUrl() method.
    return $this->urlGenerator->generate('login_fr');
  }
}

ShaOneEncoder.php

<?php

namespace App\Security;

use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class ShaOneEncoder implements UserPasswordHasherInterface
{
    private $params;

    private $em;
    
    public function __construct(EntityManagerInterface $em, ParameterBagInterface $params)
    {
      error_log(__METHOD__);
      $this->params = $params;
      $this->em = $em;
    }
    /**
     * Encodes the raw password.
     *
     * @param string      $raw  The password to encode
     * @param string|null $salt The salt
     *
     * @return string The encoded password
     *
     * @throws BadCredentialsException   If the raw password is invalid, e.g. excessively long
     * @throws \InvalidArgumentException If the salt is invalid
     */
    public function encodePassword($raw, $salt)
    {
      error_log(__METHOD__);
        return sha1($this->mergePasswordAndSalt($raw, $salt));
    }
    /**
     * Checks a raw password against an encoded password.
     *
     * @param string      $encoded An encoded password
     * @param string      $raw     A raw password
     * @param string|null $salt    The salt
     *
     * @return bool true if the password is valid, false otherwise
     *
     * @throws \InvalidArgumentException If the salt is invalid
     */
    public function isPasswordValid(UserInterface $user, $raw)
    {
      error_log(__METHOD__);
        $encodedCredentials = $this->encodePassword($raw, $salt);

        if ($encodedCredentials === $encoded) {
            return true;
        }

        return false;

    }

    /**
     * Merges a password and a salt.
     *
     * @param string $password the password to be used
     * @param string $salt     the salt to be used
     *
     * @return string a merged password and salt
     *
     * @throws \InvalidArgumentException
     */
    protected function mergePasswordAndSalt($password, $salt)
    {
      error_log(__METHOD__);
        $passwd_separator = $this->params->get('standard.passwd_separator');
        
        return $password . $passwd_separator . $salt;
    }

    public function needsRehash(UserInterface $user): bool
    {
      error_log(__METHOD__);
        return true;
    }

    /**
     * Hashes a plain password.
     *
     * @throws InvalidPasswordException When the plain password is invalid, e.g. excessively long
     */
    public function hash(string $plainPassword): string
    {
      // should get the user to retrieve his salt then
      // return $this->encodePassword($plainPassword, $salt)
      die(__METHOD__);
    }

    /**
     * Verifies a plain password against a hash.
     */
    public function verify(string $hashedPassword, string $plainPassword): bool
    {
      die(__METHOD__); // Not executed except when using PasswordHasherInterface
      return $hashedPassword === $this->hash($plainPassword);
    }
}

If I use PasswordHasherInterface script execute verify method but not when using UserPasswordHasherInterface

CodePudding user response:

Thx to @Cerad solution is to use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface that

Provides password hashing and verification capabilities for "legacy" hashers that require external salts.

https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/PasswordHasher/LegacyPasswordHasherInterface.php

  • Related