This bundle detects brute-force attacks on Symfony applications. It then will disable login for attackers for a certain period of time. This bundle also provides special events to execute custom handlers when a brute-force attack is detected.
The bundle is since version 1.0 compatible with Symfony 5.
Add this bundle via Composer:
composer require anyx/login-gate-bundle
Add in config/packages/login_gate.yml:
# config/packages/login_gate.yaml
login_gate:
storages: ['orm'] # Attempts storages. Available storages: ['orm', 'session', 'mongodb']
options:
max_count_attempts: 3
timeout: 600 #Ban period
watch_period: 3600 #Only for databases storage. Period of actuality attempts
Since Symfony does not provide a common way to retrieve passed username
from LoginFailureEvent
for every possible authentication scenario,
by default this bundle is trying to retrieve username from _username
parameter in request's form data.
It means, that if you are using different authentication scenario (json_login
, for example),
users with same ip addresses will be indistinguishable. To prevent this,
you probably should create own username resolver and register it in username_resolver
option:
<?php
namespace App\Service;
use Anyx\LoginGateBundle\Service\UsernameResolverInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Username resolver for json login
*/
class UsernameResolver implements UsernameResolverInterface
{
public function resolve(Request $request)
{
$requestData = json_decode($request->getContent(), true);
return is_array($requestData) && array_key_exists('username', $requestData) ? $requestData['username'] : null;
}
}
# config/packages/login_gate.yaml
login_gate:
storages: ['orm'] # Attempts storages. Available storages: ['orm', 'session', 'mongodb']
options:
max_count_attempts: 3
timeout: 600 #Ban period
watch_period: 3600 #Only for databases storage. Period of actuality attempts
username_resolver: App\Service\UsernameResolver
services:
acme.brute_force_listener:
class: Acme\BestBundle\Listener\BruteForceAttemptListener
tags:
- { name: kernel.event_listener, event: security.brute_force_attempt, method: onBruteForceAttempt }
For classic login form authentication we can check count login attempts before showing form:
namespace App\Controller;
use Anyx\LoginGateBundle\Service\BruteForceChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="login")
*/
public function formLogin(AuthenticationUtils $authenticationUtils, BruteForceChecker $bruteForceChecker, Request $request): Response
{
if (!$bruteForceChecker->canLogin($request)) {
return new Response('Too many login attempts');
}
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
}
Also there is ability to clear login attempts for request (it happens after successful authentication by default):
$this->bruteForceChecker->getStorage()->clearCountAttempts($request, $username);
For more examples take a look at the tests.