Skip to content

Commit 4626578

Browse files
committed
Add RateLimit middleware docs.
1 parent e8964e0 commit 4626578

File tree

3 files changed

+363
-0
lines changed

3 files changed

+363
-0
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ Error
137137
``Debugger.editorBasePath`` Configure value if defined. This improves
138138
debugging workflows within containerized environments.
139139

140+
Http
141+
----
142+
143+
- The new ``RateLimitMiddleware`` provides configurable rate limiting for your
144+
application to protect against abuse and ensure fair usage of resources. It
145+
supports multiple identification strategies (IP, user, route, API key),
146+
different rate limiting algorithms (sliding window, fixed window, token bucket),
147+
and advanced features like custom identifiers, request costs, and dynamic limits.
148+
140149
I18n
141150
----
142151

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: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
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' => RateLimitMiddleware::IDENTIFIER_IP,
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+
Constants
35+
=========
36+
37+
The middleware provides constants for common identifier and strategy values:
38+
39+
Identifier Constants
40+
--------------------
41+
42+
- ``RateLimitMiddleware::IDENTIFIER_IP`` - Client IP address (default)
43+
- ``RateLimitMiddleware::IDENTIFIER_USER`` - Authenticated user
44+
- ``RateLimitMiddleware::IDENTIFIER_ROUTE`` - Route (controller/action combination)
45+
- ``RateLimitMiddleware::IDENTIFIER_API_KEY`` - API key from token headers
46+
- ``RateLimitMiddleware::IDENTIFIER_TOKEN`` - Alias for API key
47+
48+
Strategy Constants
49+
------------------
50+
51+
- ``RateLimitMiddleware::STRATEGY_SLIDING_WINDOW`` - Sliding window algorithm (default)
52+
- ``RateLimitMiddleware::STRATEGY_FIXED_WINDOW`` - Fixed window algorithm
53+
- ``RateLimitMiddleware::STRATEGY_TOKEN_BUCKET`` - Token bucket algorithm
54+
55+
Configuration Options
56+
=====================
57+
58+
The middleware accepts the following configuration options:
59+
60+
- **limit** - Maximum number of requests allowed (default: 60)
61+
- **window** - Time window in seconds (default: 60)
62+
- **identifier** - How to identify clients. Use identifier constants (default: ``IDENTIFIER_IP``)
63+
- **strategy** - Rate limiting algorithm. Use strategy constants (default: ``STRATEGY_SLIDING_WINDOW``)
64+
- **strategyClass** - Fully qualified class name of a custom rate limiter strategy. Takes precedence over ``strategy`` option
65+
- **cache** - Cache configuration to use (default: 'default')
66+
- **headers** - Whether to include rate limit headers in responses (default: true)
67+
- **includeRetryAfter** - Whether to include Retry-After header in 429 responses (default: true)
68+
- **message** - Custom error message for rate limit exceeded (default: 'Rate limit exceeded. Please try again later.')
69+
- **ipHeader** - Header name(s) to check for client IP when behind a proxy (default: 'x-forwarded-for')
70+
- **tokenHeaders** - Array of headers to check for API tokens (default: ``['Authorization', 'X-API-Key']``)
71+
- **skipCheck** - Callback to determine if a request should skip rate limiting
72+
- **costCallback** - Callback to determine the cost of a request
73+
- **identifierCallback** - Callback to determine the identifier for a request
74+
- **limitCallback** - Callback to determine the limit for a specific identifier
75+
- **keyGenerator** - Callback for custom cache key generation
76+
- **limiters** - Array of named limiter configurations for different rate limit profiles
77+
- **limiterResolver** - Callback to resolve which named limiter applies to a request
78+
79+
Identifier Types
80+
================
81+
82+
IP Address
83+
----------
84+
85+
The default identifier type tracks requests by IP address::
86+
87+
use Cake\Http\Middleware\RateLimitMiddleware;
88+
89+
new RateLimitMiddleware([
90+
'identifier' => RateLimitMiddleware::IDENTIFIER_IP,
91+
'limit' => 100,
92+
'window' => 60,
93+
])
94+
95+
The middleware automatically handles proxy headers. You can configure
96+
which headers to check using the ``ipHeader`` option::
97+
98+
new RateLimitMiddleware([
99+
'identifier' => RateLimitMiddleware::IDENTIFIER_IP,
100+
'ipHeader' => ['CF-Connecting-IP', 'X-Forwarded-For'],
101+
])
102+
103+
User-based
104+
----------
105+
106+
Track requests per authenticated user::
107+
108+
new RateLimitMiddleware([
109+
'identifier' => RateLimitMiddleware::IDENTIFIER_USER,
110+
'limit' => 1000,
111+
'window' => 3600, // 1 hour
112+
])
113+
114+
This requires authentication middleware to be loaded before rate limiting.
115+
The middleware checks for users implementing ``Authentication\IdentityInterface``.
116+
117+
Route-based
118+
-----------
119+
120+
Apply different limits to different routes::
121+
122+
new RateLimitMiddleware([
123+
'identifier' => RateLimitMiddleware::IDENTIFIER_ROUTE,
124+
'limit' => 10,
125+
'window' => 60,
126+
])
127+
128+
This creates separate limits for each controller/action combination.
129+
130+
API Key / Token
131+
---------------
132+
133+
Track requests by API key or token::
134+
135+
new RateLimitMiddleware([
136+
'identifier' => RateLimitMiddleware::IDENTIFIER_API_KEY,
137+
'limit' => 5000,
138+
'window' => 3600,
139+
])
140+
141+
By default, the middleware looks for tokens in the ``Authorization`` and
142+
``X-API-Key`` headers. You can customize which headers to check::
143+
144+
new RateLimitMiddleware([
145+
'identifier' => RateLimitMiddleware::IDENTIFIER_TOKEN,
146+
'tokenHeaders' => ['Authorization', 'X-API-Key', 'X-Auth-Token'],
147+
])
148+
149+
Custom Identifiers
150+
==================
151+
152+
You can create custom identifiers using a callback::
153+
154+
new RateLimitMiddleware([
155+
'identifierCallback' => function ($request) {
156+
// Custom logic to identify the client
157+
$tenant = $request->getHeader('X-Tenant-ID');
158+
return 'tenant_' . $tenant[0];
159+
},
160+
])
161+
162+
Rate Limiting Strategies
163+
========================
164+
165+
Sliding Window
166+
--------------
167+
168+
The default strategy that provides smooth rate limiting by continuously
169+
adjusting the window based on request timing::
170+
171+
new RateLimitMiddleware([
172+
'strategy' => RateLimitMiddleware::STRATEGY_SLIDING_WINDOW,
173+
])
174+
175+
Fixed Window
176+
------------
177+
178+
Resets the counter at fixed intervals::
179+
180+
new RateLimitMiddleware([
181+
'strategy' => RateLimitMiddleware::STRATEGY_FIXED_WINDOW,
182+
])
183+
184+
Token Bucket
185+
------------
186+
187+
Allows for burst capacity while maintaining an average rate::
188+
189+
new RateLimitMiddleware([
190+
'strategy' => RateLimitMiddleware::STRATEGY_TOKEN_BUCKET,
191+
'limit' => 100, // bucket capacity
192+
'window' => 60, // refill rate
193+
])
194+
195+
Custom Strategy
196+
---------------
197+
198+
You can use a custom rate limiter strategy by specifying the ``strategyClass``
199+
option. Your class must implement ``Cake\Http\RateLimiter\RateLimiterInterface``::
200+
201+
new RateLimitMiddleware([
202+
'strategyClass' => App\RateLimiter\MyCustomRateLimiter::class,
203+
])
204+
205+
The ``strategyClass`` option takes precedence over the ``strategy`` option.
206+
207+
Named Limiters
208+
==============
209+
210+
For complex applications, you can define named limiter configurations
211+
and resolve them dynamically per request::
212+
213+
new RateLimitMiddleware([
214+
'limiters' => [
215+
'default' => [
216+
'limit' => 60,
217+
'window' => 60,
218+
],
219+
'api' => [
220+
'limit' => 1000,
221+
'window' => 3600,
222+
],
223+
'premium' => [
224+
'limit' => 10000,
225+
'window' => 3600,
226+
],
227+
],
228+
'limiterResolver' => function ($request) {
229+
$user = $request->getAttribute('identity');
230+
if ($user && $user->plan === 'premium') {
231+
return 'premium';
232+
}
233+
if (str_starts_with($request->getPath(), '/api/')) {
234+
return 'api';
235+
}
236+
return 'default';
237+
},
238+
])
239+
240+
Advanced Usage
241+
==============
242+
243+
Skip Rate Limiting
244+
------------------
245+
246+
Skip rate limiting for certain requests::
247+
248+
new RateLimitMiddleware([
249+
'skipCheck' => function ($request) {
250+
// Skip rate limiting for health checks
251+
return $request->getParam('action') === 'health';
252+
},
253+
])
254+
255+
Request Cost
256+
------------
257+
258+
Assign different costs to different types of requests::
259+
260+
new RateLimitMiddleware([
261+
'costCallback' => function ($request) {
262+
// POST requests cost 5x more
263+
return $request->getMethod() === 'POST' ? 5 : 1;
264+
},
265+
])
266+
267+
Dynamic Limits
268+
--------------
269+
270+
Set different limits for different users or plans::
271+
272+
new RateLimitMiddleware([
273+
'limitCallback' => function ($request, $identifier) {
274+
$user = $request->getAttribute('identity');
275+
if ($user && $user->plan === 'premium') {
276+
return 10000; // Premium users get higher limit
277+
}
278+
return 100; // Free tier limit
279+
},
280+
])
281+
282+
Custom Key Generation
283+
---------------------
284+
285+
Customize how cache keys are generated::
286+
287+
new RateLimitMiddleware([
288+
'keyGenerator' => function ($request, $identifier) {
289+
// Include the HTTP method in the key for per-method limits
290+
return $identifier . '_' . $request->getMethod();
291+
},
292+
])
293+
294+
Rate Limit Headers
295+
==================
296+
297+
When enabled, the middleware adds the following headers to responses:
298+
299+
- ``X-RateLimit-Limit`` - The maximum number of requests allowed
300+
- ``X-RateLimit-Remaining`` - The number of requests remaining
301+
- ``X-RateLimit-Reset`` - Unix timestamp when the rate limit resets
302+
303+
When a client exceeds the limit, a ``Retry-After`` header is also included
304+
(controlled by the ``includeRetryAfter`` option).
305+
306+
Multiple Rate Limiters
307+
======================
308+
309+
You can apply multiple rate limiters with different configurations::
310+
311+
// Strict limit for login attempts
312+
$middlewareQueue->add(new RateLimitMiddleware([
313+
'identifier' => RateLimitMiddleware::IDENTIFIER_IP,
314+
'limit' => 5,
315+
'window' => 900, // 15 minutes
316+
'skipCheck' => function ($request) {
317+
return $request->getParam('action') !== 'login';
318+
},
319+
]));
320+
321+
// General API rate limit
322+
$middlewareQueue->add(new RateLimitMiddleware([
323+
'identifier' => RateLimitMiddleware::IDENTIFIER_API_KEY,
324+
'limit' => 1000,
325+
'window' => 3600,
326+
]));
327+
328+
Cache Configuration
329+
===================
330+
331+
The rate limiter stores its data in cache. Make sure you have a persistent
332+
cache configured::
333+
334+
// In config/app.php
335+
'Cache' => [
336+
'rate_limit' => [
337+
'className' => 'Redis',
338+
'prefix' => 'rate_limit_',
339+
'duration' => '+1 hour',
340+
],
341+
],
342+
343+
Then use it in the middleware::
344+
345+
new RateLimitMiddleware([
346+
'cache' => 'rate_limit',
347+
])
348+
349+
.. warning::
350+
The ``File`` cache engine is not recommended for production use with
351+
rate limiting as it may not handle concurrent requests properly.

0 commit comments

Comments
 (0)