Login Throttling in Laravel 5.1

Posted on July 31, 2015 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

If you've ever run a SaaS (or put any web site with comments or signups on the Internet for any length of time), you've experienced the annoyance of spam signups and comments.

But, whether or not you know it, any login forms are likely to get a lot of automated login attempts. Most login forms don't stop an automated attack trying email after email, password after password, and since those aren't being logged, you might not even know it's happening.

The best solution to something like this is to halt a user from attempting logins after a certain number of failed attempts. This is called login throttling, or rate limiting.

Graham Campbell wrote a great package called Laravel Throttle to address this in previous versions of Laravel, but in Laravel 5.1 Login throttling comes right out of the box.

How to Enable Login Throttling in Laravel 5.1

By default, Laravel 5.1's AuthController already imports the ThrottlesLogins trait, so every new Laravel 5.1 app already has this enabled out of the box.

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

In order for it to work, you just need to display errors on your login page, which you'll likely already have because you need to display "bad username/password" type errors; something like this:

@if (count($errors) > 0)
    <div class="alert alert-danger">
        <strong>Whoops!</strong> There were some problems with your input.<br><br>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

Once you do, anyone who has 5 failed logins in a row will be stopped from logging in for 60 seconds. Both of these values are customizable; read below to see how.

Laravel Throttling in action

How Laravel 5.1's Login Throttling works

If you check out the ThrottlesLogins trait, you can see it's incrementing a cache counter on every failed login. The cache key for whether you're treated as the same user is based on username and IP address:

return 'login:attempts:'.md5($username.$request->ip());

That leaves us with code looking like this:

$attempts = $this->getLoginAttempts($request);

$lockedOut = Cache::has($this->getLoginLockExpirationKey($request));

if ($attempts > $this->maxLoginAttempts() || $lockedOut) {
    if (! $lockedOut) {
        Cache::put(
            $this->getLoginLockExpirationKey($request), time() + $this->lockoutTime(), 1
        );
    }

    return true;
}

return false;

We can also learn that we can customize how long it locks them out by simply setting a lockoutTime property on our AuthController:

private function lockoutTime() 
{
    return property_exists($this, 'lockoutTime') ? $this->lockoutTime : 60;
}

The same is true for the number of login attempts; customize that with a maxLoginAttempts property. We learn that here:

protected function maxLoginAttempts()
{
    return property_exists($this, 'maxLoginAttempts') ? $this->maxLoginAttempts : 5;
}

Conclusion

So: upgrade to Laravel 5.1 and you get free login throttling with a simple trait. Start new apps in Laravel 5.1 and they get login throttling for free out of the box. Security FTW.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.1