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.