A brute-force protection middleware for express routes that rate limits incoming requests
A brute-force protection middleware for express routes that rate-limits incoming requests, increasing the delay with each request in a fibonacci-like sequence.
$ npm install express-brute
var ExpressBrute = require'express-brute'var store = ; // stores state locally, don't use this in productionvar bruteforce = store;apppost'/auth'bruteforceprevent // error 429 if we hit this route too oftenressend'Success!';;
storeAn instance of
ExpressBrute.MemoryStoreor some other ExpressBrute store (see a list of known stores below).
freeRetriesThe number of retires the user has before they need to start waiting (default: 2)
minWaitThe initial wait time (in milliseconds) after the user runs out of retries (default: 500 milliseconds)
maxWaitThe maximum amount of time (in milliseconds) between requests the 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.
lifetimeThe 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 to
maxWait * the number of attempts before you hit maxWaitto discourage simply waiting for the lifetime to expire before resuming an attack. With default values this is about 6 hours.
failCallbackGets called with (
nextValidRequestDate) when a request is rejected (default: ExpressBrute.FailForbidden)
proxyDepthSpecifies how many levels of the
X-Forwarded-Forheader to trust. If your web server is behind a CDN and/or load balancer you'll need to set this to however many levels of proxying it's behind to get a valid IP. Setting this too high allows attackers to get around brute force protection by spoofing the
X-Forwarded-Forheader, so don't set it higher than you need to (default: 0)
attachResetToRequestSpecify whether or not a simplified reset method should be attached at
req.brute.reset. The simplified method takes only a callback, and resets all
ExpressBrutemiddleware that was called on the current request. If multiple instances of
ExpressBrutehave middleware on the same request, only those with
attachResetToRequestset to true will be reset (default: true)
refreshTimeoutOnRequestDefines whether the
lifetimecounts from the time of the last request that ExpressBrute didn't prevent for a given IP (true) or from of that IP's first request (false). Useful for allowing limits over fixed periods of time, for example: a limited number of requests per day. (Default: true). More info
handleStoreErrorGets called whenever an error occurs with the persistent store from which ExpressBrute cannot recover. It is passed an object containing the properties
message(a description of the message),
parent(the error raised by the session store), and [
ip] or [
next] depending on whether or the error occurs during
resetor in the middleware itself.
An in-memory store for persisting request counts. Don't use this in production, instead choose one of the more robust store implementations listed below.
prevent(req, res, next)Middleware that will bounce requests that happen faster than the current wait time by calling
failCallback. Equivilent to
getMiddleware(options)Generates middleware that will bounce requests with the same
keyand IP address that happen faster than the current wait time by calling
failCallback. Also attaches a function at
req.brute.resetthat can be called to reset the counter for the current ip and key. This functions the the
resetinstance method, but without the need to explicitly pass the
keycan be a string or alternatively it can be a
function(req, res, next)that or calls
next, passing a string as the first parameter.
failCallbackAllows you to override the value of
failCallbackfor this middleware
ignoreIPDisregard IP address when matching requests if set to
true. Defaults to
reset(ip, key, next)Resets the wait time between requests back to its initial value. You can pass
keyif you want to reset a request protected by
getIPFromRequest(req)Uses the current proxy trust settings to get the current IP from a request object
There are some built-in callbacks that come with BruteExpress that handle some common use cases.
ExpressBrute.FailTooManyRquestsTerminates the request and responses with a 429 (Too Many Requests) error that has a
Retry-Afterheader and a JSON error message.
ExpressBrute.FailForbiddenTerminates the request and responds with a 403 (Forbidden) error that has a
Retry-Afterheader and a JSON error message. This is provided for compatibility with ExpressBrute versions prior to v0.5.0, for new users
FailTooManyRequestsis the preferred behavior.
ExpressBrute.FailMarkSets res.nextValidRequestDate, the Retry-After header and the res.status=429, then calls next() to pass the request on to the appropriate routes.
There are a number adapters that have been written to allow ExpressBrute to be used with different persistent storage implementations, some of the ones I know about include:
require'connect-flash';var ExpressBrute = require'express-brute'MemcachedStore = require'express-brute-memcached'moment = require'moment'store;if configenvironment == 'development'store = ; // stores state locally, don't use this in productionelse// stores state with memcachedstore = '127.0.0.1'prefix: 'NoConflicts';varreqflash'error' "You've made too many failed attempts in a short period of time, please try again "+momentnextValidRequestDatefromNow;resredirect'/login'; // brute force protection triggered, send them back to the login page;var handleStoreError =log.errorerror; // log this error so we can figure out what went wrong// cause node to exit, hopefully restarting the process fixes the problemthrowmessage: errormessageparent: errorparent;// Start slowing requests after 5 failed attempts to do something for the same uservar userBruteforce = storefreeRetries: 5proxyDepth: 1minWait: 5*60*1000 // 5 minutesmaxWait: 60*60*1000 // 1 hour,failCallback: failCallbackhandleStoreError: handleStoreError;// No more than 1000 login attempts per day per IPvar globalBruteforce = storefreeRetries: 1000proxyDepth: 1attachResetToRequest: falserefreshTimeoutOnRequest: falseminWait: 25*60*60*1000 // 1 day 1 hour (should never reach this wait time)maxWait: 25*60*60*1000 // 1 day 1 hour (should never reach this wait time)lifetime: 24*60*60 // 1 day (seconds not milliseconds)failCallback: failCallbackhandleStoreError: handleStoreError;apppost'/auth'globalBruteforcepreventuserBruteforcegetMiddleware// prevent too many attempts for the same usernamenextreqbodyusername;if UserisValidLoginreqbodyusername reqbodypassword // omitted for the sake of conciseness// reset the failure counter so next time they log in they get 5 tries again before the delays kick inreqbruteresetresredirect'/'; // logged in, send them to the home page;elseresflash'error' "Invalid username or password"resredirect'/login'; // bad username/password, send them back to the login page;
.resetcallbacks are now always called asyncronously, regardless of the implementation of the store (particularly effects
handleStoreErroroption to allow more customizable handling of errors that are thrown by the persistent store. Default behavior is to throw the errors as an exception - there is nothing ExpressBrute can do to recover.
FailTooManyRequestsfailure callback, that returns a 429 (TooManyRequests) error instead of 403 (Forbidden). This is a more accurate error status code.
FailForbiddenremains an option for backwards compatiblity.
FailMarkno longer sets returns 403 Forbidden, instead does 429 TooManyRequets.
refreshTimeoutOnRequestoption that allows you to prevent the remaining
lifetimefor a timer from being reset on each request (useful for implementing limits for set time frames, e.g. requests per day)
attachResetToRequestparameter that lets you prevent the request object being decorated
failCallbackcan be overriden by
ExpressBrutethat specifies how many levels of the
X-Forwarded-Forheader to trust (inspired by express-bouncer).
getIPFromRequestmethod that essentially allows
resetto used in a similar ways as in v0.2.2. This also respects the new
getMiddlewarenow takes an options object instead of the key directly.
ExpressBruteon the same route.
lifetimenow has a reasonable default derived from the other settings for that instance of
req.brute.reset. It takes a single parameter (a callback), and will reset all the counters used by
ExpressBrutemiddleware that was called for the current route.
lifetimeis now specified on
MemcachedStore. This also means lifetime is now supported by MemoryStore.
ExpressBrute.resethas changed. It now requires an IP and key be passed instead of a request object.