I am currently working on a symfony 6.2 project with php 8.1
I have set up a custom hash class that should be used to hash the application connection password.
For that, I configured my security.yaml file as follows:
security:
password_hashers:
App\Entity\Useraccount:
id: 'App\Security\Hasher\CustomVerySecureHasher'
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
custom_authenticator: App\Security\AppCustomAuthenticator
access_control:
when@test:
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
When I execute the command "php bin/console security:hash-password testPpassword", I get the password "testPassword" hashed with my "CustomVerySecureHasher" class. When I log this class, I can see that php is passed in for hashing (logical). Here is the content of my "CustomVerySecureHasher" class (yes, the class aims to hash in md5, and I know that md5 should not be used anymore because it is not secure enough but in this case, it is only for tests, the project will never go to production):
<?php
namespace App\Security\Hasher;
use Exception;
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
class CustomVerySecureHasher implements PasswordHasherInterface
{
public function hash(string $plainPassword): string
{
// dd($plainPassword);
if (!in_array('md5', hash_algos(), true)) {
throw new Exception('MD5 is not supported by this system.');
}
return md5($plainPassword);
}
public function verify(string $hashedPassword, string $plainPassword): bool
{
return $hashedPassword === md5($plainPassword);
}
public function needsRehash(string $hashedPassword): bool
{
return false;
}
}
The problem I have is that when I use my login form (the one on my GUI), it never goes through my custom hasher class. I can log in as much as I want, I get my login and password in my "AppCustomAuthenticator" class, but the hash used is not the right one. Here is the content of my "AppCustomAuthenticator" class:
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class AppCustomAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
}
public function authenticate(Request $request): Passport
{
$login = $request->request->get('login', '');
$request->getSession()->set(Security::LAST_USERNAME, $login);
return new Passport(
new UserBadge($login),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
// For example:
// return new RedirectResponse($this->urlGenerator->generate('some_route'));
return new RedirectResponse($this->urlGenerator->generate('app_home'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
This is also my controller:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route(path: '/', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('/security/login.html.twig', ['login' => $lastUsername, 'error' => $error]);
}
#[Route(path: '/logout', name: 'app_logout')]
public function logout(): void
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
So I did the command "php bin/console debug:config security" which allows to know which algorithms are used and I have the sha512 which seems to be always present (line "hash_algorithm"). What do you think about it? Return of the command:
security:
password_hashers:
App\Entity\Useraccount:
id: App\Security\Hasher\CustomVerySecureHasher
migrate_from: { }
hash_algorithm: sha512
key_length: 40
ignore_case: false
encode_as_base64: true
iterations: 5000
cost: null
memory_cost: null
time_cost: null
providers:
users_in_memory:
memory:
users: { }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
methods: { }
user_checker: security.user_checker
stateless: false
lazy: false
required_badges: { }
main:
lazy: true
provider: users_in_memory
custom_authenticators:
- App\Security\AppCustomAuthenticator
methods: { }
security: true
user_checker: security.user_checker
stateless: false
required_badges: { }
access_control: { }
access_denied_url: null
session_fixation_strategy: migrate
hide_user_not_found: true
erase_credentials: true
enable_authenticator_manager: true
access_decision_manager:
allow_if_all_abstain: false
allow_if_equal_granted_denied: true
role_hierarchy: { }
So you get it, when I try to log in, the password entered will never be the same as the password hashed in the database since the hash system used is not the same.
Do you have any idea why my login form doesn't use my custom hash class when the "php bin/console security:hash-password testPpassword" command uses it just fine?