Skip to content

RFC: Add RateLimitMiddleware#18829

Merged
othercorey merged 6 commits into
5.nextfrom
5.next-rate-limit-middleware
Nov 8, 2025
Merged

RFC: Add RateLimitMiddleware#18829
othercorey merged 6 commits into
5.nextfrom
5.next-rate-limit-middleware

Conversation

@dereuromark

@dereuromark dereuromark commented Aug 15, 2025

Copy link
Copy Markdown
Member

Do we want to have a more native out of the box middleware for this?
The reason to have this can be

  • Better built in security
  • Good in comparison to PHP framework status quo (not having this can be a con)

For docs see cakephp/docs#8063

Frameworks with Built-in Rate Limiting

Laravel ✅

Most comprehensive implementation:

  Route::middleware(['throttle:60,1'])->group(function () {
      Route::get('/user', function () {
          //
      });
  });
  • Multiple limiters (global, user, route-based)
  • Redis and cache backends
  • Customizable responses

Symfony ✅

Added in Symfony 5.2:

  use Symfony\Component\RateLimiter\RateLimiterFactory;

  #[Route('/api/login', methods: ['POST'])]
  #[RateLimit(name: 'login', limit: 5, period: '15 minutes')]
  public function login() { }
  • Token bucket and sliding window
  • Multiple storage adapters
  • Framework integration via attributes

Spiral Framework ✅

Built-in rate limiter:

  use Spiral\RateLimiter\Middleware\RateLimiterMiddleware;

  $router->addMiddleware(new RateLimiterMiddleware(
      policy: 'api',
      maxAttempts: 100,
      window: 60
  ));

Mezzio (Laminas) ✅

Via mezzio-rate-limit package:

$app->pipe(RateLimitMiddleware::class);

Frameworks WITHOUT Built-in Rate Limiting

Yii2 ❌

Has RateLimiter behavior but requires manual implementation

CakePHP ❌

Currently requires plugins like muffin/throttle

@dereuromark

dereuromark commented Aug 15, 2025

Copy link
Copy Markdown
Member Author

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)

  1. Named Rate Limiters

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'))
  1. Route Attributes/Annotations

Symfony supports attributes on controller methods:

  // Missing in CakePHP - would need:
  #[RateLimit(limit: 5, window: 300)]
  public function login() { }
  1. Retry-After Header

Both frameworks automatically add Retry-After header:

  // Should add to our implementation:
  if (!$result['allowed']) {
      $response = $response->withHeader('Retry-After', $result['reset'] - time());
  }
  1. Hit Callback

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);
          });
  });
  1. Distributed Rate Limiting

Laravel has built-in support for distributed systems:

  return Limit::perMinute(1000)
      ->by($request->user()->id)
      ->distributed(); // Uses Redis EVAL for atomic operations
  1. Rate Limit Events

Laravel fires events when limits are hit:

  Event::listen(RateLimitExceeded::class, function ($event) {
      Log::warning('Rate limit exceeded', [
          'key' => $event->key,
          'limit' => $event->limit,
      ]);
  });
  1. Grace Period

Some implementations support grace periods:

  'limit' => 60,
  'window' => 60,
  'grace_period' => 10, // Allow 10% over limit before hard stop
  1. IP Whitelisting

Built-in IP whitelist support:

  'whitelist' => ['127.0.0.1', '192.168.1.0/24'],

Critical Missing Features

The most critical missing features are:

  1. Retry-After Header - Standard HTTP practice
  2. Distributed/Atomic Operations - Important for scaled applications
  3. Named/Reusable Limiters - Better configuration management
  4. Events/Logging - Important for monitoring and alerting

@ADmad

ADmad commented Aug 15, 2025

Copy link
Copy Markdown
Member

Comment thread src/Http/Middleware/RateLimitMiddleware.php Outdated
Comment thread src/Http/Middleware/RateLimitMiddleware.php Outdated
Comment thread src/Http/Middleware/RateLimitMiddleware.php Outdated
Comment thread src/Http/Middleware/RateLimitMiddleware.php Outdated
Comment on lines +52 to +59
$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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be data races here, but with the current Cache interface, I don't think we can resolve that issue.

Comment thread tests/TestCase/Http/Middleware/RateLimitMiddlewareTest.php
@dereuromark

Copy link
Copy Markdown
Member Author

This is more to get an idea of General 1st level Support of this security topic within the framework directly
Also what kind of the above features would be good to have. Then we can go into details and also clarify those.
Indeed, it should be more general by default.

@ADmad

ADmad commented Aug 16, 2025

Copy link
Copy Markdown
Member

Retry-After Header - Standard HTTP practice
Distributed/Atomic Operations - Important for scaled applications
Named/Reusable Limiters - Better configuration management
Events/Logging - Important for monitoring and alerting

Retry-After header and events are something that should be present in the initial release. Other features could be added later.

Comment thread src/Http/RateLimit/FixedWindowRateLimiter.php Outdated
Comment thread src/Http/RateLimit/FixedWindowRateLimiter.php
@ADmad ADmad requested a review from Copilot August 16, 2025 06:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot AI Aug 16, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
$window = 3600; // Assume max window of 1 hour for reset
public function reset(string $identifier, int $window): void
{
$now = time();

Copilot uses AI. Check for mistakes.
Comment thread src/Http/Middleware/RateLimitMiddleware.php
@dereuromark

Copy link
Copy Markdown
Member Author

Compared to Throttle this would include

  1. Multiple rate limiting strategies (sliding window, token bucket, fixed window) vs Throttle's single approach
  2. More built-in identifier types (ip, user, route, api_key) with configurable headers
  3. Cost-based rate limiting for different weight operations
  4. Retry-After header support
  5. More flexible configuration with callbacks for dynamic limits

@LordSimal LordSimal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍🏻

@dereuromark dereuromark added the needs squashing The pull request should be squashed before merging label Nov 4, 2025
@othercorey othercorey merged commit 45d92d5 into 5.next Nov 8, 2025
13 of 15 checks passed
@othercorey othercorey deleted the 5.next-rate-limit-middleware branch November 8, 2025 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement needs squashing The pull request should be squashed before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants