RFC: Add RateLimitMiddleware#18829
Conversation
|
While this is a basic setup for this now, i wonder if we are OK with this as is. What's Missing in current Implementation (yet)
Laravel and Symfony both support named limiters that can be reused: // Missing in CakePHP - would need:
RateLimiter::define('api', [
'limit' => 100,
'window' => 60,
]);
// Then use:
->add(new RateLimitMiddleware('api'))
Symfony supports attributes on controller methods: // Missing in CakePHP - would need:
#[RateLimit(limit: 5, window: 300)]
public function login() { }
Both frameworks automatically add Retry-After header: // Should add to our implementation:
if (!$result['allowed']) {
$response = $response->withHeader('Retry-After', $result['reset'] - time());
}
Laravel supports callbacks when limit is hit: RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()->id)
->response(function () {
return response('Custom response...', 429);
});
});
Laravel has built-in support for distributed systems: return Limit::perMinute(1000)
->by($request->user()->id)
->distributed(); // Uses Redis EVAL for atomic operations
Laravel fires events when limits are hit: Event::listen(RateLimitExceeded::class, function ($event) {
Log::warning('Rate limit exceeded', [
'key' => $event->key,
'limit' => $event->limit,
]);
});
Some implementations support grace periods: 'limit' => 60,
'window' => 60,
'grace_period' => 10, // Allow 10% over limit before hard stop
Built-in IP whitelist support: 'whitelist' => ['127.0.0.1', '192.168.1.0/24'],Critical Missing Features The most critical missing features are:
|
| $count = (int)$this->cache->get($key, 0); | ||
| $allowed = $count + $cost <= $limit; | ||
|
|
||
| if ($allowed) { | ||
| $count += $cost; | ||
| $ttl = $windowStart + $window - $now; | ||
| $this->cache->set($key, $count, $ttl); | ||
| } |
There was a problem hiding this comment.
There will be data races here, but with the current Cache interface, I don't think we can resolve that issue.
|
This is more to get an idea of General 1st level Support of this security topic within the framework directly |
Retry-After header and events are something that should be present in the initial release. Other features could be added later. |
There was a problem hiding this comment.
Pull Request Overview
Introduces a comprehensive built-in rate limiting middleware for CakePHP, addressing security needs and bringing the framework's capabilities in line with other major PHP frameworks. The implementation provides multiple rate limiting strategies to handle various use cases effectively.
- Adds native rate limiting middleware with configurable strategies (sliding window, token bucket, fixed window)
- Implements multiple identification methods (IP, user, route, API key) with flexible callback system
- Creates standard HTTP 429 exception handling for rate limit violations
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Http/Exception/TooManyRequestsException.php | HTTP 429 exception class for rate limit violations |
| src/Http/Middleware/RateLimitMiddleware.php | Main middleware implementation with configurable strategies and identifiers |
| src/Http/RateLimit/RateLimiterInterface.php | Interface defining rate limiter contract |
| src/Http/RateLimit/FixedWindowRateLimiter.php | Fixed time window rate limiting implementation |
| src/Http/RateLimit/SlidingWindowRateLimiter.php | Sliding window rate limiting with decay algorithm |
| src/Http/RateLimit/TokenBucketRateLimiter.php | Token bucket rate limiting implementation |
| tests/TestCase/Http/Middleware/RateLimitMiddlewareTest.php | Comprehensive middleware test coverage |
| tests/TestCase/Http/RateLimit/SlidingWindowRateLimiterTest.php | Sliding window rate limiter unit tests |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| public function reset(string $identifier): void | ||
| { | ||
| $now = time(); | ||
| $window = 3600; // Assume max window of 1 hour for reset |
There was a problem hiding this comment.
The hardcoded window value of 3600 seconds in the reset method is a magic number that may not match the actual window used. Consider storing the window duration in the cache key or as an instance variable to ensure accurate reset behavior.
| $window = 3600; // Assume max window of 1 hour for reset | |
| public function reset(string $identifier, int $window): void | |
| { | |
| $now = time(); |
|
Compared to Throttle this would include
|
Do we want to have a more native out of the box middleware for this?
The reason to have this can be
For docs see cakephp/docs#8063
Frameworks with Built-in Rate Limiting
Laravel ✅
Most comprehensive implementation:
Symfony ✅
Added in Symfony 5.2:
Spiral Framework ✅
Built-in rate limiter:
Mezzio (Laminas) ✅
Via mezzio-rate-limit package:
Frameworks WITHOUT Built-in Rate Limiting
Yii2 ❌
Has RateLimiter behavior but requires manual implementation
CakePHP ❌
Currently requires plugins like muffin/throttle