I am using form_login in two different firewalls, one for user, one for admin. I want this to only affect the user one.
What would be the right implementation to have recaptcha on login form?
Some things I'm considering:
I created a bundle for this question: https://packagist.org/packages/syspay/login-recaptcha-bundle
Old response:
What I did to solve this problem:
I created a new Security Listener Factory called CaptchaLoginFormFactory which has the following
<?php
namespace Project\Bundle\CoreBundle\DependencyInjection\Security\Factory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
/**
* CaptchaLoginFormFactory
*/
class CaptchaLoginFormFactory extends FormLoginFactory
{
/**
* {@inheritdoc}
*/
public function getKey()
{
return 'form_login_captcha';
}
/**
* {@inheritdoc}
*/
protected function getListenerId()
{
return 'security.authentication.listener.form_login_captcha';
}
}
and a new Authentication Listener called CaptchaFormAuthenticationListener
<?php
namespace Project\Bundle\CoreBundle\Security\Firewall;
use Project\Security\CaptchaManager;
use Project\Security\Exception\InvalidCaptchaException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
use Symfony\Component\Security\Http\ParameterBagUtils;
/**
* CaptchaFormAuthenticationListener
*/
class CaptchaFormAuthenticationListener extends UsernamePasswordFormAuthenticationListener
{
/** @var CaptchaManager $captchaManager */
private $captchaManager;
/**
* setCaptchaManager
*
* @param CaptchaManager $captchaManager
*/
public function setCaptchaManager(CaptchaManager $captchaManager)
{
$this->captchaManager = $captchaManager;
}
/**
* {@inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if ($this->captchaManager->isCaptchaNeeded($request)) {
$requestBag = $this->options['post_only'] ? $request->request : $request;
$recaptchaResponse = ParameterBagUtils::getParameterBagValue($requestBag, 'g-recaptcha-response');
if (!$this->captchaManager->isValidCaptchaResponse($recaptchaResponse, $request->getClientIp())) {
throw new InvalidCaptchaException();
}
}
return parent::attemptAuthentication($request);
}
}
As can be seen they extend the original FormFactory with some changes where before I use the normal authentication listener I use my own methods to validate the captcha.
Then I added it in the CoreBundle::build method
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new CaptchaLoginFormFactory());
}
and created the services
security.authentication.listener.form_login_captcha:
class: Project\Bundle\CoreBundle\Security\Firewall\CaptchaFormAuthenticationListener
parent: security.authentication.listener.form
abstract: true
calls:
- [ setCaptchaManager, ['@project.security.captcha_manager'] ]
Then in security.yml under the firewall I just use the new factory form_login_captcha with the same options as form_login. This way I can use form_login on another firewall without affecting it at all.