0

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?

Spinogl
  • 15
  • 6
  • FYI: You should **never** use `MD5` to store passwords. Have a look at [password_hash](https://stackoverflow.com/questions/30279321/how-to-use-phps-password-hash-to-hash-and-verify-passwords) on how to store passwords properly. – DarkBee Mar 06 '23 at 09:54
  • I know very well that md5 should not be used. I already noted this in my post but I'll put it back here. The question is not really about md5 itself. The question is more about using a custom hash system. It doesn't matter if it's md5 or something else, if I don't use one of the hashes built into symfony, the problem will still be the same. – Spinogl Mar 06 '23 at 09:58
  • Try changing this `App\Entity\Useraccount:` in your *security.yaml* to a tag instead like the [docs](https://symfony.com/doc/current/security/passwords.html#creating-a-custom-password-hasher) as `app_hasher`? – Bossman Mar 06 '23 at 11:08
  • I just tried to replace the line with app_hasher. The behaviour remains the same. The form doesn't use my CustomVerySecureHasher class, unlike the security:hash-password command – Spinogl Mar 06 '23 at 11:14
  • It sort of looks like you are using the in memory user provider though, like your previous almost identical question, I suspect you are not showing everything in security.yaml. The in memory user provider returns a InMemoryUser so the hasher will not be used. You can try replacing `App\Entity\Useraccount` with the original `Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface` and see if that helps. You have to understand that Symfony allows specifying different hashers for different user types. – Cerad Mar 06 '23 at 16:40
  • The security.yaml I sent you is complete. I have nothing more in mine. I've tried several things instead of AppEntityUseraccount including what you just quoted but none of it works. I also have no problem with the multiple hashes thing. Is there not a way to force the use of a single hash for the whole project? I don't need any more in my case? Is there a configuration in php.ini that could block the thing otherwise? – Spinogl Mar 06 '23 at 16:48
  • You have not defined any in memory users so why do you expect to be able to authenticate? – Cerad Mar 06 '23 at 16:57
  • Even before defining users in memory, if my form doesn't go through my custom hash class, it can't work. I added these lines to the security.yaml "providers: app_user_provider: entity: class: App\Entity\Useraccount" but this obviously doesn't work as I still don't pass in my custom hash class. The problem is not that the connection doesn't work, the problem is that I'm not passing in my custom hash class (and so, it does indeed follow that the connection doesn't work, but when I get to my custom hash class, the connection should be fine) – Spinogl Mar 07 '23 at 08:02
  • The `form` (I think you mean the AppCustomAuthenticator::authenticate) pulls out the submitted plaintext password and adds it to the Passport. The hasher comes into play later when the passport is processed by internal Symfony code. I hate to say it but you are making this far more difficult that it has to be. – Cerad Mar 07 '23 at 13:10

0 Answers0