Monitoring Failed Login Attempts With Laravel
Laravel
Laravel’s login throttling is a valuable combatant against dictionary attacks.
the user will not be able to login for one minute if they fail to provide the correct credentials after several attempts.
Login throttling hinders and impedes potential dictionary attacks, however, it doesn’t prevent them entirely, an attacker could wait for one minute and try again. In order to identify malicious activity and take action to stop it, you can record failed login attempts; this doesn’t completely thwart hackers either, but it can be the groundwork for some further security tools like:
- Letting the user know somebody tried logging into their account.
- Blocking the IP address until you can investigate.
- Locking the users account until you can verify ownership.
Laravel introduced the Illuminate\Auth\Events\Failed
event in 5.2, which is fired when the guest provides invalid credentials when logging in.
Failed
has two public properties, $user
and $credentials
. If the provided email address exists in the database, the $user
property is an instance of the User
model associated with that address, otherwise, the $user
property is null
. You can guess what $credentials
is.
Whenever the Failed
event is fired an event listener will record the failed attempt. Then we will have a log of all failed login attempts that we can use to improve security.
To begin with let’s make a new LoginAttempt
model and a migration file to go with it.
php artisan make:model FailedLoginAttempt --migration
In the migration file add the following.
Schema::create('failed_login_attempts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->nullable();
$table->string('email_address');
$table->ipAddress('ip_address');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
user_id
The user’s ID helps us to identify an attack on a particular account.
email_address
The email address allows us to determine whether the guest has misspelt their email, which could explain the failed login attempts.
ip_address
We should not assume that an intruder will consecutively target the same email address. Therefore, we also record the IP address as well to identify when the same source is attempting several addresses.
class FailedLoginAttempt extends Modal
{
protected $fillable = [
'user_id', 'email_address', 'ip_address',
];
public static function record($user = null, $email, $ip)
{
return static::create([
'user_id' => is_null($user) ? null : $user->id,
'email_address' => $email,
'ip_address' => $ip,
]);
}
}
Now create a RecordFailedLoginAttempt
event listener and register it in the EventServiceProvider
.
php artisan make:listener RecordFailedLoginAttempt --event=Illuminate\\Auth\\Events\\Failed
class RecordFailedLoginAttempt
{
public function handle(Failed $event)
{
\App\FailedLoginAttempt::record(
$event->user,
$event->credentials['email'],
request()->ip()
);
}
}
class EventServiceProvider extends ServiceProvider
{
...
protected $listen = [
\Illuminate\Auth\Events\Failed::class => [
\App\Listeners\RecordFailedLoginAttempt::class,
],
];
...
}