Skip to content

Conversation

timacdonald
Copy link
Member

@timacdonald timacdonald commented Sep 20, 2025

This PR continues the amazing work, by @JosephSilber, in #56933 by extending it to allow for a generic callback hook.

Currently, it is only possible to rate limit based on the request. Often, it can be useful to rate limit based on the response.

From the original PR: For example, imagine you have a sign up endpoint that could return validation errors. If you want to throttle to a single sign up per day for an IP address, any validation response would trigger the rate limiter making it impossible to sign up again for the day.

Another example is rate limiting 404 responses to mitigate enumeration attacks on resource identifiers. If a single user hits a certain level of 404 responses in a short time frame, they could be attempting enumeration attacks.

Below is an example of an after limiter that would restrict user's from hitting too many 404 responses in a single minute:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

/*
 * Ensure a user can only hit ten 404 responses in a minute before they are
 * rate limited to ensure user's cannot enumerate resource IDs.
 */
RateLimiter::for('resource-not-found', function (Request $request) {
    return Limit::perMinute(10)
        ->by("user:{$request->user()->id}")

        // The new `after` hook...

        ->after(function (Response $response) {                 
            return $response->getStatusCode() === 404;
        });
});

Yet to be solved caveat

Due to the nature of after limiters, it is possible that more than 10 requests could come through per minute. Given the above limiter, if there were already 9 requests and then two requests came in at the same time, a race condition occurs where two requests could make it through. This would effectively mean that 11 requests were made within the minute.

I think we could solve this but I've left it for now to open discussion.

Initial solution idea: I was thinking we could offer the ability to use a cache lock to pause the second incoming request until the first has finished. Replicating what the Route::block method achieves:

->after(function (Response $response) {                 
    return $response->getStatusCode() === 404;
}, lockSeconds: 10, waitSeconds: 10);

This would use the limit's key to determine when to block.

Copy link

Thanks for submitting a PR!

Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@timacdonald timacdonald changed the title [12.x] Allow named rate limiters to be hit based on the response [12.x] Introduce "after" rate limiting Sep 20, 2025
@taylorotwell
Copy link
Member

taylorotwell commented Sep 22, 2025

I dig this... trying to decide how I feel about after vs. something like whenResponse:

RateLimiter::for('resource-not-found', function (Request $request) {
    return Limit::perMinute(10)
        ->by("user:{$request->user()->id}")
        ->whenResponse(fn (Response $response)  => $response->getStatusCode() === 404)
});

But even whenResponse I'm not sure I love since it sounds like the 404s will be limited when in actuality they are not counted.

I think I'm fine with punting on your caveat for now and wait to see community demand, but if it's pretty easy to add your suggest lock / wait stuff that's fine too!

@JosephSilber
Copy link
Contributor

Are we going to have helper methods for success/404?

@JosephSilber
Copy link
Contributor

But even whenResponse I'm not sure I love since it sounds like the 404s will be limited when in actuality they are not counted.

Maybe hitOnResponse or something like that?

@timacdonald timacdonald marked this pull request as ready for review September 29, 2025 01:01
@timacdonald
Copy link
Member Author

I've realised that the standard ThrottleRequests mechanism has a race condition that could allow more requests than specified in the limit through, so I'm not going to worry about adding the blocking mechanism to this feature.

Regarding naming, I think I still lean towards after. I liked after because I felt it framed it as changing the order of how the limiter works, e.g., it is an after limiter. whenResponse feels like it might only limit 404s. hitOnResponse might be more accurate but also feels more verbose than I personally would have liked.

@taylorotwell taylorotwell merged commit e32b3a6 into laravel:12.x Sep 29, 2025
64 of 65 checks passed
@taylorotwell
Copy link
Member

Thanks!

@JosephSilber
Copy link
Contributor

Seeems like this comment was lost in the shuffle:

Are we going to have helper methods for success/404?

@timacdonald timacdonald deleted the rate-limit-on-success branch September 29, 2025 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants