Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5: how to reset builtin throttle / ratelimiter?

Im using Laravel's builtin throttle like this:

//File: Kernal
protected $middlewareGroups = [
'api' => ['throttle:10,3']
];

However, I would like to reset the count after certain action in one of my controllers (for example after successful login).

I can see this middleware uses RateLimiter and that has a public method called clear.

The problem is, how to use this? Because it depends upon the key from ThrottleRequests middleware.

  1. To get the object of ThrottleRequests I need instance of RateLimiter
  2. To get the object of RateLimiter, I need instance of Cache. . .

all in all, there is no end to how to use it.. Any idea?

Thanks

like image 932
Raheel Hasan Avatar asked May 02 '18 03:05

Raheel Hasan


1 Answers

As your question is tagged with Laravel v5.5, here's what applies there:

For Login attempts specifically:

You can use the Illuminate\Foundation\Auth\AuthenticatesUsers trait in your controller, so you would have access to the clearLoginAttempts method, which calls the clear() method on the RateLimiter instance with the correct key without the need to provide the key.

Actually if you look at how Illuminate\Foundation\Auth\ThrottlesLogins::clearLoginAttempts() is implemented, you can see that the right key can be retrieved by $this->throttleKey($request), once your controller uses the AuthenticatesUsers trait.

In general:

You can always get the Illuminate\Cache\RateLimiter instance by using app(\Illuminate\Cache\RateLimiter::class), which would in turn contain all the configured limiters and the cache as well. The problem is that it is not possible to get the cache keys from this point of view. So you really have to find out where and how the key was set in the first place, so you can use the same key for the reset.

The standard ThrottleRequests middleware sets the key in the handle() method, but the actual key would depend on where and how your throttling is configured (e.g.: is it a named limiter or only set using the numeric parameters, was ->by(...) called on it to set the key explicitly etc.)

If you only need to find the key for one particular limiter, probably you can set a breakpoint in the handle() method and just check.

Your case

In your particular case, as it is not a named limiter, the handle() method will call resolveRequestSignature to get the key. I don't think you can easily access the Middleware instance from a Controller. What you can do is to check how that method generates the key and basically copy that piece of code to replicate the same keys, but I'd not recommend that as it is a dirty and fragile solution. Ifyou check, you'll see the key can be reproduced as something like:

if ($user = $request->user()) {
    $key = sha1($user->getAuthIdentifier());
}
elseif ($route = $request->route()) {
    $key = sha1($route->getDomain().'|'.$request->ip());
}

But in more recent Laravel versions you can explicitly set the key which is much cleaner and reliable solution:


In Laravel 8

Now as the question is fairly old, most people would rather use the latest version of Laravel (v8 as of 2021/02/12), so for them the documentation includes the way to "segment" the limiters aka. become able to apply separate limit counters for different requests based on the request (or session data, etc.). In fact the by() method actually sets the key of the limiter. So you can set up one or more named limiters like:

RateLimiter::for('my_per_ip_limiter', function (Request $request) {
    return Limit::perMinute(100)->by($request->ip());
});

This means the limiter named my_per_ip_limiter will use the IP as the key, so any time in your controllers you could call:

app(\Illuminate\Cache\RateLimiter::class)->clear($request->ip());

to reset the limiter for a particular IP. Or to get the number of attempts so far:

$attempts_so_far = app(\Illuminate\Cache\RateLimiter::class)->attempts($request->ip());

Indeed instead of IP you could use any variable of the request (or session or whatever).

However there is no way (I think) to differentiate between the named limiters. So if the same key is used for another limiter as well, their hits will be counted together* and clear together. So giving a name like my_per_ip_limiter to the limiter is only useful so you can assign that limiter to particular routes by the name, e.g.:

Route::post( 'login', 'Auth\LoginController@login' )
       ->middleware('throttle:my_per_ip_limiter');

But if you really need named limiters to reset individually, you have to use a unique key, for example prefixing it with something, e.g.:

RateLimiter::for('my_other_ip_limiter', function (Request $request) {
    return Limit::perMinute(100)->by('other_'.$request->ip());
});

This can be cleared independently from the other one:

// reset my_other_ip_limiter, but not my_per_ip_limiter :
app(\Illuminate\Cache\RateLimiter::class)->clear('other_'.$request->ip()); 

*: By counted together I mean they would add up, so if you apply two of them to the same request, each single request will bump the counter by 2!

like image 95
sbnc.eu Avatar answered Nov 12 '22 20:11

sbnc.eu