Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Merklin's avatar

Laravel rate-limiting package

Will be glad to hear your thoughts and get some feedback on this one: https://github.com/milenmk/laravel-rate-limiting

1 like
7 replies
Merklin's avatar

@Tray2 If you want to change the limit, you have to create/extend a service or provider to set the new limit, or add it to the AppService provider.

  1. The package requires no code to be written. Everything is inside a single config file.

  2. There are blade components for showing a warning and showing an error when the limit is about to be reached. For example, if you have set a maximum of 3 attempts for login, they will see a warning like You have 2 attempts left ...

  3. When the limit set is exceeded, a lock is implied, and the lock is progressive. Like: Limit of 3 attempts and linear lock increase

    • 3 attempts: just shows a warning
    • after 4th attempt: 1 minute lock
    • 5th attempt before the lock expires: new lock implied for 2 minutes
    • 6th attempt before the lock expires: new lock implied for 3 minutes

All of this is configurable and extends the basic limit settings.

Also, there are 3 backoff strategies to impose the limits: linear, Fibonacci and exponential.

Most of this is explained and shown in the README file. Or to summarise:

This package essentially takes Laravel's basic rate-limiting capabilities and extends them with sophisticated features specifically tailored for authentication security, making it much easier to implement robust protection against brute force attacks without having to build all these features yourself.
1 like
vincent15000's avatar

@Merklin > If you want to change the limit, you have to create/extend a service or provider to set the new limit, or add it to the AppService provider.

Is it really a problem ?

1 like
Merklin's avatar

@vincent15000 Yes, it is easy to write something like this:

RateLimiter::for('login', function (Request $request) {
            $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());

            return Limit::perMinute(5)->by($throttleKey);
        });

But when we add the registration, the 2FA, the reset password, and even a few additional keys, it can get messy.

With the package, you only have to edit the limit number in here:

'max_attempts' => (int) env('RATE_LIMITING_TWO_FACTOR_SESSION_MAX_ATTEMPTS', 5),

from 5 to what you want.

But the main feature is that the package applies an incrementing limiter decay.

With a regular limit, you imply it for, let's say, 60 seconds. When the limit is reached, you wait, and after it expires, if the limit is hit again, it is again 60 seconds.

But if you try during the wait time, nothing happens. And here comes this package. On limit exceeding, the lock is 60 seconds. If you try it again during the lock, in is increased to 120 seconds, and the timer (wait time) starts again. If you try again, it becomes 180 seconds and so on.

Also, look at my comment below.

1 like
JussiMannisto's avatar

Disclosure: I haven't installed the package, I just glanced at the repo quickly, so I may have misunderstood things. And I'm not sure how the limiters from the config are actually applied.

From what I gather, the package seems quite opinionated and complex for such a low-level feature as rate limiting. I'm sure it fits some projects, but I think it's too specific to be broadly adoptable.

It uses Blade, and it's not clear to me how it would work with Inertia or any API calls (e.g. from a SPA).

I don't see it setting response headers anywhere, such as Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, etc. Are the configured rate limiters applied via Laravel's ThrottleRequests middleware, which would add the headers?

It's not clear to me how you would set a base decay time in the config. Is it hard-coded?

If you want to change the limit, you have to create/extend a service or provider to set the new limit, or add it to the AppService provider.

You don't have to do that. You can just use the throttle middleware with raw parameters:

Route::get('my-api')->middleware('throttle:20,1');

You can of course define a custom limiter in a service provider, but you can also do rate limiting on the fly:

$passed = RateLimiter::attempt(
	key: 'some-action:'.$request->user()->id, 
	maxAttempts: 3, 
	callback: fn() => $this->doTheThing(),
	decaySeconds: 30
);

if(!$passed)
	return back()->with('error', 'Chill!');
else
	return back()->with('success', 'Ok.');

Overall, I like my rate limiters to do one thing: rate limiting. I don't want them to be concerned with the view part of MVC.

The inclusion of different growth strategies is definitely interesting, but at the same time, their configuration seems very rigid to me. Unless I'm missing something.

1 like
Merklin's avatar

@JussiMannisto The package uses exactly the native Laravel rate limiting as a base.

  1. Using the views is not required, as they are just components that you can call in a blade file to show a warning for the attempts left or the wait time. The last can easily be handmade. I've included the views just for convenience.

  2. I can also say that the package might have a very narrow or targeted audience. For most of the apps, basic rate limiting is enough.

  3. As said, the main pros of the package are that it is easier to change the limit attempts per action, and you can set a progressive decay using 3 different ways.

1 like

Please or to participate in this conversation.