-
-
Notifications
You must be signed in to change notification settings - Fork 162
ExpressBrute migration
Advantages of migration:
- Keep limiting logic with the same options and API.
- Use atomic increments to count allowed requests. Get/set approach implemented in
ExpressBrute
may get you into trouble. - Remove unnecessary
long-timeout
andunderscore
dependencies.
Note: there is another way to protect login endpoints described here. It has much better performance and flexibility as it counts failed attempts only.
-
freeRetries
The number of retries the user has before they need to start waiting (default: 2) -
minWait
The initial wait time (in milliseconds) after the user runs out of retries (default: 1000 milliseconds) -
maxWait
The maximum amount of time (in milliseconds) between requests user needs to wait (default: 15 minutes). The wait for a given request is determined by adding the time the user needed to wait for the previous two requests. -
lifetime
The length of time (in seconds since the last request) to remember the number of requests that have been made by an IP. By default, it will be set tomaxWait * the number of attempts before you hit maxWait
to discourage simply waiting for the lifetime to expire before resuming an attack. With default values this is about 6 hours. -
failCallback
Gets called with (req
,resp
,next
,nextValidRequestDate
) when a request is rejected (default: ExpressBruteFlexible.FailForbidden) -
attachResetToRequest
Specify whether or not a simplified reset method should be attached atreq.brute.reset
.(default: true) -
refreshTimeoutOnRequest
Alwaysfalse
for ExpressBruteFlexible. -
handleStoreError
Gets called whenever an error occurs with the persistent store from which ExpressBruteFlexible cannot recover. It is passed an object containing the propertiesmessage
(a description of the message),parent
(the error raised by the session store), and [key
,ip
] or [req
,res
,next
] depending on whether or the error occurs duringreset
or in the middleware itself. -
prefix
Useful when several middlewares should have different counters for the same key. (default: '')
-
key
can be a string or alternatively it can be afunction(req, res, next)
that callsnext
, passing a string as the first parameter. -
failCallback
Set custom callback on the final fail after all attempts. -
ignoreIP
Disregard IP address when matching requests if set totrue
. (default: false) -
prefix
Useful when several middlewares should have different counters for the same key. (default: '')
req.brute.reset
can be called for current request, if attachResetToRequest
is true. Alternatively, reset(ip, key, callback)
can be called from ExpressBruteFlexible instance. The first argument ip
should be set to NULL, if ignoreIp
option is true
.
const ExpressBruteFlexible = require('rate-limiter-flexible/lib/ExpressBruteFlexible');
const redis = require('redis');
const http = require('http');
const express = require('express');
const redisClient = await redis.createClient({
enable_offline_queue: false,
})
.connect();
const opts = {
freeRetries: 10,
minWait: 1000, // 1 second
maxWait: 10000, // 10 seconds
lifetime: 30, // 30 seconds
storeClient: redisClient,
};
const bruteforce = new ExpressBruteFlexible(
ExpressBruteFlexible.LIMITER_TYPES.REDIS,
opts
);
const app = express();
app.post('/auth',
bruteforce.prevent, // error 429 if we hit this route too often
function (req, res, next) {
res.send('Success!');
}
);
ExpressBruteFlexible
constructor requires to set a limiter type one from ExpressBruteFlexible.LIMITER_TYPES.*
.
The second argument is options.
Options are the same except:
-
storeClient
should be added in case of using any limiter type except MEMORY and CLUSTER. -
dbName
may be set if necessary. It depends on limiter type. -
tableName
may be set if all limits data should be stored in one table. -
storeType
should be set to 'knex', if it is used.
Other notes:
-
ExpressBruteFlexible
always works withrefreshTimeoutOnRequest=false
option. - it works only with seconds since
rate-limiter-flexible
duration is in seconds. For example, ifminWait=500
it is1 second
.
Express app is launched in 4 processes with redis:5.0.4-alpine
in Docker container.
const opts = {
freeRetries: 20,
minWait: 1000,
maxWait: 10000,
lifetime: 30,
// the next option is for ExpressBrute, as ExpressBruteFlexible works only with fixed window
refreshTimeoutOnRequest: false,
};
./bombardier -c 500 -l -d 30s -r 1000 -t 5s
shoots two endpoints limited by ExpressBrute and ExpressBruteFlexible 1000 times per second during 30 seconds. Every request is assigned with one of 100 random keys.
Statistics Avg Stdev Max
Reqs/sec 997.49 275.09 3663.47
Latency 5.97ms 6.07ms 84.51ms
Latency Distribution
50% 4.17ms
75% 5.24ms
90% 10.14ms
95% 16.06ms
99% 37.72ms
HTTP codes:
1xx - 0, 2xx - 2884, 3xx - 0, 4xx - 27109, 5xx - 0
Statistics Avg Stdev Max
Reqs/sec 1000.76 440.44 3796.22
Latency 23.94ms 19.53ms 204.91ms
Latency Distribution
50% 16.79ms
75% 28.64ms
90% 49.46ms
95% 66.53ms
99% 105.55ms
HTTP codes:
1xx - 0, 2xx - 2601, 3xx - 0, 4xx - 27395, 5xx - 0
Atomic increments slow down requests processing. If your authorization endpoint processes more than 1000 requests per second, test ExpressBruteFlexible
before going to production with it.
Get started
Middlewares and plugins
Migration from other packages
Limiters:
- Redis
- Memory
- DynamoDB
- Prisma
- MongoDB (with sharding support)
- PostgreSQL
- MySQL
- BurstyRateLimiter
- Cluster
- PM2 Cluster
- Memcached
- RateLimiterUnion
- RateLimiterQueue
Wrappers:
- RLWrapperBlackAndWhite Black and White lists
Knowledge base:
- Block Strategy in memory
- Insurance Strategy
- Comparative benchmarks
- Smooth out traffic peaks
-
Usage example
- Minimal protection against password brute-force
- Login endpoint protection
- Websocket connection prevent flooding
- Dynamic block duration
- Different limits for authorized users
- Different limits for different parts of application
- Block Strategy in memory
- Insurance Strategy
- Third-party API, crawler, bot rate limiting