composer require anyx/login-gate-bundle
config/bundles.php:
return [
// ...
Anyx\LoginGateBundle\AnyxLoginGateBundle::class => ['all' => true],
];
config/packages/login_gate.yaml:
login_gate:
storages: ['orm'] # or ['session'] for stateless apps
options:
max_count_attempts: 3
timeout: 600 # Ban duration in seconds (10 mins)
use Anyx\LoginGateBundle\Service\BruteForceChecker;
public function login(AuthenticationUtils $authUtils, BruteForceChecker $bruteForceChecker, Request $request): Response
{
if (!$bruteForceChecker->canLogin($request)) {
return $this->json(['error' => 'Too many attempts'], 429);
}
// Proceed with normal login logic
}
Request Handling:
BruteForceChecker into controllers/services.canLogin($request) before allowing login attempts.if (!$bruteForceChecker->canLogin($request)) {
$this->addFlash('error', 'Account temporarily locked');
return $this->redirectToRoute('login');
}
Storage Strategies:
storages: ['orm']
storages: ['session']
storages: ['mongodb']
Custom Username Resolution:
Override default _username form field for APIs/JSON logins:
// src/Service/CustomUsernameResolver.php
class CustomUsernameResolver implements UsernameResolverInterface {
public function resolve(Request $request) {
return json_decode($request->getContent(), true)['email'] ?? null;
}
}
Register in config:
login_gate:
username_resolver: App\Service\CustomUsernameResolver
Event-Driven Extensions: Listen for brute-force events to trigger custom actions (e.g., notifications):
services:
App\EventListener\BruteForceListener:
tags:
- { name: kernel.event_listener, event: security.brute_force_attempt, method: onAttempt }
public function onAttempt(BruteForceAttemptEvent $event) {
$this->mailer->send(new AlertEmail($event->getUsername()));
}
Manual Reset: Clear attempts after successful login:
public function loginSuccess(AuthenticationSuccessEvent $event) {
$this->bruteForceChecker->getStorage()->clearCountAttempts(
$event->getRequest(),
$event->getUser()->getUsername()
);
}
IP-Based Lockouts:
session storage for stateless apps.Username Resolution Failures:
_username field may break with custom auth providers (e.g., json_login). Always implement a UsernameResolverInterface for non-form logins.ORM Storage Quirks:
watch_period (default: 3600s) determines how old attempts are purged. Set too low (e.g., 60s) and you’ll lose accuracy; too high and storage bloat occurs.(ip, username, created_at).Event Ordering:
security.brute_force_attempt event fires after the attempt is counted. If you need to block immediately, check canLogin() first.Session Storage Limitations:
Check Attempts Manually:
$attempts = $bruteForceChecker->getStorage()->getCountAttempts($request, $username);
$this->logger->debug('Attempts for '.$username, ['attempts' => $attempts]);
Clear All Attempts (Dev Only):
$this->bruteForceChecker->getStorage()->clearAllAttempts();
Log Events:
Extend the BruteForceAttemptEvent to include metadata:
public function onAttempt(BruteForceAttemptEvent $event) {
$this->logger->warning(
'Brute force attempt',
['ip' => $event->getRequest()->getClientIp(), 'user' => $event->getUsername()]
);
}
Custom Storage:
Implement AttemptStorageInterface for alternative backends (e.g., Redis):
class RedisStorage implements AttemptStorageInterface {
public function countAttempts(Request $request, string $username): int { /* ... */ }
// Implement other methods...
}
Register in config:
login_gate:
storages: ['@app.redis_storage']
Dynamic Thresholds:
Adjust max_count_attempts per user role:
$maxAttempts = $user->isAdmin() ? 5 : 3;
$this->bruteForceChecker->setMaxAttempts($maxAttempts);
Rate Limiting Middleware:
Combine with Symfony’s RateLimiter for API endpoints:
use Symfony\Component\Security\Http\Firewall\RateLimiter;
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
if ($request->isMethod('POST') && $request->getPathInfo() === '/api/login') {
$limiter = new RateLimiter(3, 600);
if (!$limiter->isAllowed($request->getClientIp())) {
return new Response('Too many requests', 429);
}
}
}
Two-Factor Workarounds: If using 2FA, reset attempts after MFA success:
public function onMfaSuccess(MfaEvent $event) {
$this->bruteForceChecker->getStorage()->clearCountAttempts(
$event->getRequest(),
$event->getUser()->getUsername()
);
}
How can I help you explore Laravel packages today?