Open
Description
Laravel Version
11
PHP Version
8.1.4
Database Driver & Version
No response
Description
The current ThrottleRequests
middleware incorrectly handles rate limits when:
- Multiple distinct keys (e.g.,
user_id
+product_id
) are used in the same limiter. - Order of limits in the array affects functionality (e.g., per-day limit checked before per-minute).
Steps To Reproduce
Issue 1: Over-Throttling with Multiple Distinct Keys
- Define a limiter:
RateLimiter::for('update-product', function (Request $request) {
return [
Limit::perDay(1)->by('user:' . $request->user()->id),
Limit::perDay(1)->by('product:' . $request->product->id)
];
});
- Scenario:
- User 1 updates Product 1 → 200 OK.
- User 2 updates Product 1 → 429 (Correct: product limit).
- User 2 tries to update Product 2 → 429 (Wrong: should be allowed!).
- Expected:
- Throttling should only apply to the violated key (product_id).
Issue 2: Order-Sensitive Limits in Documentation
-
Description:
The documentation example shows limits ordered as perMinute then perDay, but reversing the order breaks functionality. -
Broken Example:
RateLimiter::for('uploads', function (Request $request) {
return [
Limit::perDay(1000)->by($request->user()->id), // Long-term first
Limit::perMinute(10)->by($request->user()->id), // Short-term second
];
});
- Scenario:
- Send 1000 requests in 1 minute.
- Expected:
- After a minute, the user can upload but cannot.
Proposed Solution
Modify src/Illuminate/Routing/Middleware/ThrottleRequests.php
to:
- Check all limits first, then apply
hit()
.
Code Fix:
protected function handleRequest($request, Closure $next, array $limits) {
// Check all limits first
foreach ($limits as $limit) {
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts)) {
throw $this->buildException(...);
}
}
// Apply hits if all checks pass
foreach ($limits as $limit) {
$this->limiter->hit($limit->key, $limit->decaySeconds);
}
return $next($request);
}
Suggested Labels: bug
, rate limiting