Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Login Gate Bundle Laravel Package

anyx/login-gate-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:
    composer require anyx/login-gate-bundle
    
  2. Enable the Bundle: Add to config/bundles.php:
    return [
        // ...
        Anyx\LoginGateBundle\AnyxLoginGateBundle::class => ['all' => true],
    ];
    
  3. Basic Configuration: Create 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)
    
  4. First Use Case: Protect your login controller:
    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
    }
    

Implementation Patterns

Core Workflow

  1. Request Handling:

    • Inject BruteForceChecker into controllers/services.
    • Call canLogin($request) before allowing login attempts.
    if (!$bruteForceChecker->canLogin($request)) {
        $this->addFlash('error', 'Account temporarily locked');
        return $this->redirectToRoute('login');
    }
    
  2. Storage Strategies:

    • ORM: Persistent across sessions (recommended for production).
      storages: ['orm']
      
    • Session: Stateless (cleared on session end).
      storages: ['session']
      
    • MongoDB: For non-relational setups.
      storages: ['mongodb']
      
  3. 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
    
  4. 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()));
    }
    
  5. Manual Reset: Clear attempts after successful login:

    public function loginSuccess(AuthenticationSuccessEvent $event) {
        $this->bruteForceChecker->getStorage()->clearCountAttempts(
            $event->getRequest(),
            $event->getUser()->getUsername()
        );
    }
    

Gotchas and Tips

Common Pitfalls

  1. IP-Based Lockouts:

    • The bundle locks by IP + Username combination. If your app uses IP-based auth (e.g., API keys), this may cause unintended blocks. Use session storage for stateless apps.
  2. Username Resolution Failures:

    • Default _username field may break with custom auth providers (e.g., json_login). Always implement a UsernameResolverInterface for non-form logins.
  3. ORM Storage Quirks:

    • The 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.
    • For high-traffic apps, consider adding a database index on (ip, username, created_at).
  4. Event Ordering:

    • The security.brute_force_attempt event fires after the attempt is counted. If you need to block immediately, check canLogin() first.
  5. Session Storage Limitations:

    • Session-based storage is not persistent across devices/browsers. Use only for low-risk or internal apps.

Debugging Tips

  1. Check Attempts Manually:

    $attempts = $bruteForceChecker->getStorage()->getCountAttempts($request, $username);
    $this->logger->debug('Attempts for '.$username, ['attempts' => $attempts]);
    
  2. Clear All Attempts (Dev Only):

    $this->bruteForceChecker->getStorage()->clearAllAttempts();
    
  3. 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()]
        );
    }
    

Extension Points

  1. 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']
    
  2. Dynamic Thresholds: Adjust max_count_attempts per user role:

    $maxAttempts = $user->isAdmin() ? 5 : 3;
    $this->bruteForceChecker->setMaxAttempts($maxAttempts);
    
  3. 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);
            }
        }
    }
    
  4. 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()
        );
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware