Home » Php » Symfony3 – How to override the UsernamePasswordFormAuthenticationListener __construct() method safely?

Symfony3 – How to override the UsernamePasswordFormAuthenticationListener __construct() method safely?

Posted by: admin June 30, 2018 Leave a comment

Questions:

I’m trying to incorporate reCaptcha into my Symfony project login, I’ve managed to do so by overriding the attemptAuthentication(Request $request) method in UsernamePasswordFormAuthenticationListener from the Symfony http security component. I did so by adding a compiler pass on the Symfony listener – works like a charm.

I came across a problem however when trying to write out an error after unsuccessful Captcha authorization – here is what my overriden attemptAuthentication(Request $request) looks like now:

protected function attemptAuthentication(Request $request)
{
    $validator = new CaptchaValidator();
    $captchaResponse = $request->request->get('g-recaptcha-response');
    $remoteip = $request->getClientIp();

    if ($validator->checkCaptcha($captchaResponse, $remoteip))
        return parent::attemptAuthentication($request);
    else
        return new RedirectResponse('login/captcha-failure');
}

and what I would like to do is something like this:

protected function attemptAuthentication(Request $request)
{
    $validator = new CaptchaValidator();
    $captchaResponse = $request->request->get('g-recaptcha-response');
    $remoteip = $request->getClientIp();

    if ($validator->checkCaptcha($captchaResponse, $remoteip))
        return parent::attemptAuthentication($request);
    else {
        $this->session->getFlashBag()->add('error', 'captcha_error_here');
        $url = $this->router->generate('fos_user_security_login');
        return new RedirectResponse($url);
    }
}

As you can see, my temporary solution works since it redirects to a controller which handles the error etc. but it’s a very naughty solution with static routes and also the fact that anyone can access this particular route and play with the reCaptcha error is wrong.

The original listener class accepts 11 parameters for its __construct() method:

public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null)

and I would like to override the __construct() in my overriden listener and add Router $router and Session $session parameters in addition to the original 11.

In the listener itself, that’s easy, I’m worried about registering this listener as a service in services.yml. I work in PhpStorm and it helps me immensely by autofilling most of the parameters with appropriate arguments when registering as a service (I also did some digging and made sure by comparing the arguments with the ones in Symfony security_listeners.xml config file), however, there are parameters such as $providerKey and array $options. What do I pass to those so that the login stays secure?

What should the services.yml registration look like?

user.username_password_form_authentication_listener:
    class: UserBundle\EventListener\UsernamePasswordFormAuthenticationListener
    arguments:
      - "@security.token_storage"
      - "@security.authentication.manager"
      - "@security.authentication.session_strategy"
      - "@security.http_utils"
      - "???" # providerKey
      - "@security.authentication.success_handler"
      - "@security.authentication.failure_handler"
      - "???" # options
      - "@logger"
      - "@event_dispatcher"
      - "@security.csrf.token_manager"
      - "@router"
      - "@session"
    tags:
      -
          name: "kernel.event_listener"
          event: "???"
          method: "attemptAuthentication"

Should I leave those two empty? And I’m also not really sure which event does the original listener listen to.

Thanks.

Answers: