People tend to be bad at picking strong passwords. Publicly available lists of passwords recovered from hacked databases reveal that, despite efforts to educate, users still pick the same highly predictable passwords over and over. These lists make it easy for brute-force attackers to gain unauthorized access to a large number of your users' accounts.
Complicated password policies (other than password length) tend to backfire spectacularly. A good alternative then, is to slow down brute-force attackers to the point where it would take an inordinate amount of time to crack all but the easiest passwords.
This strategy is known as throttling, and should be employed in any route that could allow an attacker to gain unauthorized access or otherwise affect other users' accounts, such as the login and password recovery routes. UserFrosting supports throttling based on either IP address, or some other chosen pieces of information (e.g. username).
Throttles can be defined in your configuration file, under the throttles
key. This key contains a list of named throttle event types, each of which contains the following properties:
method
: Specifies the rule by which this throttle should be applied. If set to ip
, then attempts on this event type from the same IP address will be rate-limited. If set to data
, then attempts will be rate-limited based on the contents of the submitted request data. This can be used, for example, to limit sign-in attempts based on the username or email address.interval
: The window of time in which to consider past attempts on this event type, in deciding whether or not to allow access.delays
: A mapping of event counts (x) to delays (y, seconds). Any event of this throttle's type that has occurred more than x times in the interval, must wait y seconds after the last occurrence before another attempt is permitted.For example, consider the throttle rule:
'sign_in_attempt' => [
'method' => 'ip',
'interval' => 3600,
'delays' => [
2 => 5,
3 => 10,
4 => 20,
5 => 40,
6 => 80,
7 => 600
]
],
This defines a throttleable event sign_in_attempt
, which throttles based on IP address. Each time this event is attempted, UserFrosting checks the throttles
database table for any events of the same type from the same IP address in the past 3600 seconds. If fewer than 2 are found, the request is allowed to proceed immediately. Otherwise, the user receives an error with a 429
status code, and is told that they need to wait the specified number of seconds (as defined in delays
) before they can attempt their request again.
To disable a throttle, or all throttles completely, simply set the specific event key or the
throttles
key (respectively) tonull
.
Once you have defined your throttles, you must incorporate them into the desired controller methods. The procedure is generally defined as:
This process is generally implemented using the getDelay
and logEvent
methods on the throttler
service. For example, the following is an example from the AccountController::forgotPassword
method:
...
/** @var UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
$throttler = $this->ci->throttler;
$throttleData = [
'email' => $data['email']
];
$delay = $throttler->getDelay('password_reset_request', $throttleData);
if ($delay > 0) {
$ms->addMessageTranslated("danger", "RATE_LIMIT_EXCEEDED", [
"delay" => $delay
]);
return $response->withStatus(429);
}
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction( function() use ($data, $throttler, $throttleData) {
// Log throttleable event
$throttler->logEvent('password_reset_request', $throttleData);
...
});
You'll notice that we first check the password_reset_request
throttle (the client IP address is automatically retrieved by the throttler
service) and return an error if the computed delay is greater than 0. We do this before the call to logEvent
- which adds a record of this attempt to the database - so that requests which are rejected because of the throttle rule do not further exacerbate the timeout period.
IP-based throttling will not protect you as well from distributed attacks. One popular alternative, throttling based on username, opens your application to denial-of-service attacks because an attacker can simply lock users out of their accounts for potentially long periods of time by repeatedly making failed attempts on each account. See the OWASP guide for more information on mitigating brute-force attacks.