Closed josecelano closed 10 years ago
FOSUserBundle is not responsible for handling the login form. The authentication is provided by SecurtiyBundle, nit by FOSUserBundle
Then, could I do this:
http://symfony.com/doc/current/cookbook/security/custom_password_authenticator.html
with FOSUserBundle whithout compatibility problems?
Dou you have any idea how can I do that without patching SecuriteBundle?
FOSUserBundle is about managing users. It does not interfer with the authentication layer
Hi, Use this: How To Insert Symfony2 Captcha To Login Page In FOSUserBundle http://webmuch.com/how-to-insert-symfony2-captcha-to-login-page-in-fosuserbundle/
Thanks @gauravg47 . I did somethink like this: http://stackoverflow.com/questions/14788828/adding-captcha-to-symfony2-login-page but I am goging to change it and do my own authentication controller as it is in your link. I think it is a better solution (less coupled to Symfony).
did it worked josecelano ?
I have made this work by overriding the UsernamePasswordFormAuthenticationListener
class of SecurityBundle
(Symfony 3.4 + FOSUserBundle 2.x)
@tuanalumi how exactly did you do it? I got the recaptcha in form using EWZRecaptchaBundle
, but having trouble figuring out how to validate it in attemptAuthentication
.
Nevermind, I found a way, just not a very nice one:
<!-- services.xml -->
<service id="UserBundle\Security\Listener\UsernamePasswordFormAuthenticationListener"
decorates="security.authentication.listener.form.main">
<argument key="$authenticationListener" type="service" id="UserBundle\Security\Listener\UsernamePasswordFormAuthenticationListener.inner"/>
<argument key="$httpUtils" type="service" id="security.http_utils"/>
<argument key="$failureHandler" type="service" id="security.authentication.failure_handler.main.form_login"/>
</service>
<?php
declare(strict_types=1);
namespace UserBundle\Security\Listener;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrue as IsValidRecaptcha;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\LockedException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener as BaseListener;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use UserBundle\Service\FailureCounterService;
/**
* Class UsernamePasswordFormAuthenticationListener.
*/
class UsernamePasswordFormAuthenticationListener implements ListenerInterface
{
/**
* @var ValidatorInterface
*/
private $validator;
/**
* @var FailureCounterService
*/
private $failureCounterService;
/**
* @var BaseListener
*/
private $authenticationListener;
/**
* @var HttpUtils
*/
private $httpUtils;
/**
* @var AuthenticationFailureHandlerInterface
*/
private $failureHandler;
public function __construct(
ValidatorInterface $validator,
FailureCounterService $failureCounterService,
BaseListener $authenticationListener,
HttpUtils $httpUtils,
AuthenticationFailureHandlerInterface $failureHandler
) {
$this->validator = $validator;
$this->failureCounterService = $failureCounterService;
$this->authenticationListener = $authenticationListener;
$this->httpUtils = $httpUtils;
$this->failureHandler = $failureHandler;
}
/**
* @param GetResponseEvent $event
*/
public function handle(GetResponseEvent $event): void
{
$request = $event->getRequest();
// TODO: hardcoded options, extract from container
if (false === $request->isMethod('POST') && false === $this->httpUtils->checkRequestPath($request, '/login_check')) {
return;
}
if ($this->failureCounterService->isHardThresholdFailureCountReached('login')) {
// hard lock, login denied
$this->setExceptionResponse($event, $request, new LockedException());
return;
}
if ($this->failureCounterService->isSoftThresholdFailureCountReached('login')) {
// soft lock, a valid captcha is required
$resp = $this->validator->validate(null, new IsValidRecaptcha());
if ($resp->count() > 0) {
$this->setExceptionResponse($event, $request, new CustomUserMessageAuthenticationException('security.captcha.required'));
return;
}
}
$this->authenticationListener->handle($event);
}
/**
* @param GetResponseEvent $event
* @param Request $request
* @param AuthenticationException $exception
*/
private function setExceptionResponse(GetResponseEvent $event, Request $request, AuthenticationException $exception): void
{
$event->setResponse($this->failureHandler->onAuthenticationFailure($request, $exception));
}
}
@dkarlovi I did something like below. Note: I override the login form and use javascript to add reCaptcha (no extra bundle).
// file: src/UserBundle/SecurityListener/FormAuthenticationListener.php
namespace UserBundle\SecurityListener;
use GuzzleHttp\Client;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
use UserBundle\Exception\InvalidReCaptchaException;
class FormAuthenticationListener extends UsernamePasswordFormAuthenticationListener
{
public function attemptAuthentication(Request $request)
{
if (!$this->verifyRecaptcha($request->request->get('g-recaptcha-response'))) {
throw new InvalidReCaptchaException();
}
return parent::attemptAuthentication($request);
}
private function verifyRecaptcha($recaptchaResponse)
{
$client = new Client();
$response = $client->post(
'https://www.google.com/recaptcha/api/siteverify',
[
'form_params' => [
'secret' => 'lol, nice try',
'response' => $recaptchaResponse,
]
]
);
$result = json_decode($response->getBody(), true);
return !empty($result['success']);
}
}
//file: src/UserBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php
namespace UserBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use UserBundle\Controller\SecurityController;
use UserBundle\SecurityListener\FormAuthenticationListener;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container
->getDefinition('security.authentication.listener.form')
->setClass(FormAuthenticationListener::class)
;
$container
->getDefinition('fos_user.security.controller')
->setClass(SecurityController::class)
;
}
}
I am trying to add a Captcha to Login form whithout patching Symfony core and using FOSUserBundle.
I do not find any class in FOSUserBundle where to check the captcha.
I have added the captcha to the form template but when I do the submit the captcha is not validated.
I use my own LoginFormType.