41man/login-gate-bundle
Symfony bundle that detects brute-force login attacks and temporarily blocks offending clients. Configurable max attempts, ban timeout and watch period, with ORM/session/MongoDB storage options and an event to hook custom handlers. Includes a checker service for allow/deny and clearing attempts.
Install the Bundle
composer require anyx/login-gate-bundle
Ensure your config/bundles.php includes:
Anyx\LoginGateBundle\AnyxLoginGateBundle::class => ['all' => true],
Configure Basic Settings
Add to config/packages/login_gate.yaml (or config.yml for Symfony <5):
login_gate:
storages: ['orm'] # Default storage (ORM, session, or MongoDB)
options:
max_count_attempts: 3 # Lock after 3 failed attempts
timeout: 600 # Ban duration in seconds (10 mins)
watch_period: 3600 # For ORM storage: how long to keep attempts (1 hour)
First Use Case: Protect Login Endpoint
Inject BruteForceChecker into your login controller/service:
use Anyx\LoginGateBundle\Service\BruteForceChecker;
public function login(Request $request, BruteForceChecker $checker)
{
if (!$checker->canLogin($request)) {
return $this->json(['error' => 'Too many attempts. Try again later.'], 429);
}
// Proceed with authentication...
}
Pre-Authentication Check
Call canLogin($request) before processing login credentials. Example:
if (!$checker->canLogin($request)) {
$this->addFlash('error', 'Account locked due to brute-force attempts.');
return $this->redirectToRoute('login');
}
Post-Failure Handling After a failed login, the bundle auto-increments attempts. Manually clear on success:
try {
$authenticator->authenticate($request);
$checker->getStorage()->clearCountAttempts($request);
} catch (AuthenticationException $e) {
// Attempts are auto-tracked; no action needed.
}
Custom Storage Integration
For non-ORM storage (e.g., session), ensure your Request has a unique identifier (e.g., session_id or user_ip). Example:
login_gate:
storages: ['session']
IP-Based vs. User-Based Locking The bundle defaults to IP-based tracking. For user-specific locks (e.g., after account creation), extend the storage:
$checker->getStorage()->setUserIdentifier($request, $user->getEmail());
Event-Driven Extensions
Listen for security.brute_force_attempt to log or notify admins:
services:
App\EventListener\BruteForceLogger:
tags:
- { name: kernel.event_listener, event: security.brute_force_attempt, method: onAttempt }
public function onAttempt(BruteForceAttemptEvent $event) {
if ($event->getAttempts() >= 3) {
Mailer::sendAlert($event->getIp());
}
}
Dynamic Thresholds
Override max_count_attempts per route/user:
$checker->setMaxAttempts($request, 5); // Temporarily allow 5 attempts
Storage Mismatches
User entity with failedAttempts and lastAttempt fields. Run migrations if using auto-generated tables.session_start() isn’t called before canLogin(). Ensure middleware/session config is correct.mongodb PHP extension and a login_attempts collection.Request Identifier Conflicts
The bundle uses $request->getClientIp() by default. For shared IPs (e.g., cloud hosting), override:
$checker->setRequestIdentifier($request, $request->get('user_agent') . $request->getClientIp());
Race Conditions
High-traffic sites may see false positives due to concurrent requests. Use watch_period to purge stale attempts:
watch_period: 1800 # Keep attempts relevant for 30 mins
Event Ordering
The security.brute_force_attempt event fires after the attempt is counted. Avoid modifying $event->getAttempts() directly—use setMaxAttempts() for dynamic rules.
$checker->getStorage()->logAttempt($request, true); // Force log
$checker->getStorage()->clearCountAttempts($request);
SELECT * FROM user WHERE failed_attempts > 0;
Custom Storage
Implement Anyx\LoginGateBundle\Storage\StorageInterface for new backends (e.g., Redis):
class RedisStorage implements StorageInterface {
public function countAttempts(Request $request) { /* ... */ }
public function incrementAttempts(Request $request) { /* ... */ }
// ...
}
Register it in config/packages/login_gate.yaml:
login_gate:
storages: ['redis']
Override Default Logic
Extend BruteForceChecker to add logic (e.g., whitelist IPs):
class CustomBruteForceChecker extends BruteForceChecker {
protected function isAllowed(Request $request) {
if ($this->isWhitelisted($request)) {
return true;
}
return parent::isAllowed($request);
}
}
Bind it in services.yaml:
Anyx\LoginGateBundle\Service\BruteForceChecker: '@App\Service\CustomBruteForceChecker'
Two-Factor Workarounds If using 2FA, bypass brute-force checks for 2FA endpoints:
if ($request->getPathInfo() === '/login/2fa') {
return true; // Skip check
}
return $checker->canLogin($request);
How can I help you explore Laravel packages today?