Skip to content

Commit 00dceea

Browse files
committed
Add RateLimit middleware docs.
1 parent 8dd8e06 commit 00dceea

File tree

3 files changed

+263
-0
lines changed

3 files changed

+263
-0
lines changed

en/appendices/5-3-migration-guide.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ Command
6464
- ``cake server`` now supports a ``--frankenphp`` option that will start the
6565
development server with `FrankenPHP <https://frankenphp.dev/>`__.
6666

67+
Http
68+
----
69+
70+
- The new ``RateLimitMiddleware`` provides configurable rate limiting for your
71+
application to protect against abuse and ensure fair usage of resources. It
72+
supports multiple identification strategies (IP, user, route, API key),
73+
different rate limiting algorithms (sliding window, fixed window, token bucket),
74+
and advanced features like custom identifiers, request costs, and dynamic limits.
75+
6776
Cache
6877
-----
6978

en/controllers/middleware.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ CakePHP provides several middleware to handle common tasks in web applications:
5757
* :doc:`Cake\Http\Middleware\SecurityHeadersMiddleware </security/security-headers>`
5858
makes it possible to add security related headers like ``X-Frame-Options`` to
5959
responses.
60+
* :doc:`Cake\Http\Middleware\RateLimitMiddleware </controllers/middleware/rate-limit>`
61+
provides configurable rate limiting to protect against abuse and ensure fair
62+
usage of resources.
6063

6164
.. _using-middleware:
6265

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
Rate Limiting Middleware
2+
########################
3+
4+
.. versionadded:: 5.3
5+
6+
The ``RateLimitMiddleware`` provides configurable rate limiting for your
7+
application to protect against abuse and ensure fair usage of resources.
8+
9+
Basic Usage
10+
===========
11+
12+
To use rate limiting in your application, add the middleware to your
13+
middleware queue::
14+
15+
// In src/Application.php
16+
use Cake\Http\Middleware\RateLimitMiddleware;
17+
18+
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
19+
{
20+
$middlewareQueue
21+
// ... other middleware
22+
->add(new RateLimitMiddleware([
23+
'limit' => 60, // 60 requests
24+
'window' => 60, // per 60 seconds
25+
'identifier' => 'ip', // based on IP address
26+
]));
27+
28+
return $middlewareQueue;
29+
}
30+
31+
When a client exceeds the rate limit, they will receive a
32+
``429 Too Many Requests`` response.
33+
34+
Configuration Options
35+
=====================
36+
37+
The middleware accepts the following configuration options:
38+
39+
- **limit** - Maximum number of requests allowed (default: 60)
40+
- **window** - Time window in seconds (default: 60)
41+
- **identifier** - How to identify clients: 'ip', 'user', 'route', 'api_key' (default: 'ip')
42+
- **strategy** - Rate limiting algorithm: 'sliding_window', 'fixed_window', 'token_bucket' (default: 'sliding_window')
43+
- **cache** - Cache configuration to use (default: 'default')
44+
- **headers** - Whether to include rate limit headers in responses (default: true)
45+
- **message** - Custom error message for rate limit exceeded
46+
- **skipCheck** - Callback to determine if a request should skip rate limiting
47+
- **costCallback** - Callback to determine the cost of a request
48+
- **identifierCallback** - Callback to determine the identifier for a request
49+
- **limitCallback** - Callback to determine the limit for a specific identifier
50+
51+
Identifier Types
52+
================
53+
54+
IP Address
55+
----------
56+
57+
The default identifier type tracks requests by IP address::
58+
59+
new RateLimitMiddleware([
60+
'identifier' => 'ip',
61+
'limit' => 100,
62+
'window' => 60,
63+
])
64+
65+
The middleware automatically handles proxy headers like ``X-Forwarded-For``
66+
and ``CF-Connecting-IP``.
67+
68+
User-based
69+
----------
70+
71+
Track requests per authenticated user::
72+
73+
new RateLimitMiddleware([
74+
'identifier' => 'user',
75+
'limit' => 1000,
76+
'window' => 3600, // 1 hour
77+
])
78+
79+
This requires authentication middleware to be loaded before rate limiting.
80+
81+
Route-based
82+
-----------
83+
84+
Apply different limits to different routes::
85+
86+
new RateLimitMiddleware([
87+
'identifier' => 'route',
88+
'limit' => 10,
89+
'window' => 60,
90+
])
91+
92+
This creates separate limits for each controller/action combination.
93+
94+
API Key
95+
-------
96+
97+
Track requests by API key::
98+
99+
new RateLimitMiddleware([
100+
'identifier' => 'api_key',
101+
'limit' => 5000,
102+
'window' => 3600,
103+
])
104+
105+
The middleware looks for API keys in the ``X-API-Key`` header or
106+
``api_key`` query parameter.
107+
108+
Custom Identifiers
109+
==================
110+
111+
You can create custom identifiers using a callback::
112+
113+
new RateLimitMiddleware([
114+
'identifierCallback' => function ($request) {
115+
// Custom logic to identify the client
116+
$tenant = $request->getHeader('X-Tenant-ID');
117+
return 'tenant_' . $tenant[0];
118+
},
119+
])
120+
121+
Rate Limiting Strategies
122+
========================
123+
124+
Sliding Window
125+
--------------
126+
127+
The default strategy that provides smooth rate limiting by continuously
128+
adjusting the window based on request timing::
129+
130+
new RateLimitMiddleware([
131+
'strategy' => 'sliding_window',
132+
])
133+
134+
Fixed Window
135+
------------
136+
137+
Resets the counter at fixed intervals::
138+
139+
new RateLimitMiddleware([
140+
'strategy' => 'fixed_window',
141+
])
142+
143+
Token Bucket
144+
------------
145+
146+
Allows for burst capacity while maintaining an average rate::
147+
148+
new RateLimitMiddleware([
149+
'strategy' => 'token_bucket',
150+
'limit' => 100, // bucket capacity
151+
'window' => 60, // refill rate
152+
])
153+
154+
Advanced Usage
155+
==============
156+
157+
Skip Rate Limiting
158+
------------------
159+
160+
Skip rate limiting for certain requests::
161+
162+
new RateLimitMiddleware([
163+
'skipCheck' => function ($request) {
164+
// Skip rate limiting for health checks
165+
return $request->getParam('action') === 'health';
166+
},
167+
])
168+
169+
Request Cost
170+
------------
171+
172+
Assign different costs to different types of requests::
173+
174+
new RateLimitMiddleware([
175+
'costCallback' => function ($request) {
176+
// POST requests cost 5x more
177+
return $request->getMethod() === 'POST' ? 5 : 1;
178+
},
179+
])
180+
181+
Dynamic Limits
182+
--------------
183+
184+
Set different limits for different users or plans::
185+
186+
new RateLimitMiddleware([
187+
'limitCallback' => function ($request, $identifier) {
188+
$user = $request->getAttribute('identity');
189+
if ($user && $user->plan === 'premium') {
190+
return 10000; // Premium users get higher limit
191+
}
192+
return 100; // Free tier limit
193+
},
194+
])
195+
196+
Rate Limit Headers
197+
==================
198+
199+
When enabled, the middleware adds the following headers to responses:
200+
201+
- ``X-RateLimit-Limit`` - The maximum number of requests allowed
202+
- ``X-RateLimit-Remaining`` - The number of requests remaining
203+
- ``X-RateLimit-Reset`` - Unix timestamp when the rate limit resets
204+
- ``X-RateLimit-Reset-Date`` - ISO 8601 date when the rate limit resets
205+
206+
Multiple Rate Limiters
207+
======================
208+
209+
You can apply multiple rate limiters with different configurations::
210+
211+
// Strict limit for login attempts
212+
$middlewareQueue->add(new RateLimitMiddleware([
213+
'identifier' => 'ip',
214+
'limit' => 5,
215+
'window' => 900, // 15 minutes
216+
'skipCheck' => function ($request) {
217+
return $request->getParam('action') !== 'login';
218+
},
219+
]));
220+
221+
// General API rate limit
222+
$middlewareQueue->add(new RateLimitMiddleware([
223+
'identifier' => 'api_key',
224+
'limit' => 1000,
225+
'window' => 3600,
226+
]));
227+
228+
Cache Configuration
229+
===================
230+
231+
The rate limiter stores its data in cache. Make sure you have a persistent
232+
cache configured::
233+
234+
// In config/app.php
235+
'Cache' => [
236+
'rate_limit' => [
237+
'className' => 'Redis',
238+
'prefix' => 'rate_limit_',
239+
'duration' => '+1 hour',
240+
],
241+
],
242+
243+
Then use it in the middleware::
244+
245+
new RateLimitMiddleware([
246+
'cache' => 'rate_limit',
247+
])
248+
249+
.. warning::
250+
The ``File`` cache engine is not recommended for production use with
251+
rate limiting as it may not handle concurrent requests properly.

0 commit comments

Comments
 (0)