phpsymfonysymfony-ratelimiter

Symfony5: login throttling failing must be an instance of RequestRateLimiterInterface, instance of RateLimiterFactory given


In order to satisfy a security audit, I need to implement a feature where a user can have at most 3 attempts of login per 5 minutes

fortunately, it is now built-in Symfony 5.2: https://symfony.com/blog/new-in-symfony-5-2-login-throttling

As the parameter max_attempts is not enough for me (it's per 1 minute), I want to use the second option to provide my owner limiter

so I use this https://symfony.com/blog/new-in-symfony-5-2-rate-limiter-component

my configuration looks like this

framework:
    rate_limiter:
        max_login_failure:
            policy: fixed_window
            limit: 3
            interval: '10 minutes'
security:
    firewalls:
        admins:
            host: admin.*
            pattern: ^/
            provider: admins
            login_throttling:
                limiter: limiter.max_login_failure

but it fails with the error

Argument 2 passed to Symfony\Component\Security\Http\EventListener\LoginThrottlingListener::__construct() must be an instance of Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface, instance of Symfony\Component\RateLimiter\RateLimiterFactory given,

My understanding is that I'm giving it a factory instead of an instance of what I want, but there's nowhere in the doc where I can find how to get an instance from the factory


Solution

  • If you want to use a custom limiter you need to create an own limiter. Then define as service in services.yml An example that works for me

    <?php
    
    declare(strict_types=1);
    
    namespace App\Limiter;
    
    use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\RateLimiter\LimiterInterface;
    use Symfony\Component\RateLimiter\RateLimit;
    use Symfony\Component\RateLimiter\RateLimiterFactory;
    
        class CustomLimiter implements RequestRateLimiterInterface
        {
            /**
             * @var LimiterInterface
             */
            private $limiter;
        
            public function __construct(RateLimiterFactory $factory)
            {
                $this->limiter = $factory->create('a_unique_identifier');
            }
        
            public function consume(Request $request): RateLimit
            {
    
        
                return $this->limiter->consume();
            }
        
            public function reset(Request $request): void
            {
                $this->limiter->reset();
            }
        
        }
    

    In services.yml define a custom limiter. You should pass as an argument your max_limiter configuration

       app.custom.limiter:
        class: App\Limiter\CustomLimiter
        arguments:
            - '@limiter.max_login_failure'
    

    In the end, you should paste your new limiter to the security configuration

    security:
    firewalls:
        admins:
            host: admin.*
            pattern: ^/
            provider: admins
            login_throttling:
                limiter: app.custom.limiter
    

    That's all! I hope this helps you