Skip to content

ExpressBrute migration

Roman edited this page Apr 8, 2019 · 23 revisions

Migration example

Advantages of migration:

  1. Keep limiting logic with the same options and API.
  2. Use atomic increments to count allowed requests. Get/set approach implemented in ExpressBrute may get you into trouble.
  3. Remove unnecessary long-timeout and underscore dependencies.
const ExpressBruteFlexible = require('rate-limiter-flexible/lib/ExpressBruteFlexible');
const redis = require('redis');
const http = require('http');
const express = require('express');

const redisClient = redis.createClient({
  enable_offline_queue: false,
});

const opts = {
  freeRetries: 10,
  minWait: 1000,
  maxWait: 10000,
  lifetime: 30,
  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:

  1. storeClient should be added in case of using any limiter type except MEMORY and CLUSTER.
  2. dbName may be set if necessary. It depends on limiter type.
  3. tableName may be set if all limits data should be stored in one table.
  4. storeType should be set to 'knex', if it is used.

Other notes:

  1. ExpressBruteFlexible always works with refreshTimeoutOnRequest=false option.
  2. it works only with seconds since rate-limiter-flexible duration is in seconds. For example, if minWait=500 it is 1 second.

Benchmark

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.

ExpressBrute

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

ExpressBruteFlexible

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

Conclusion

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.