/laravel-authentication-log

Log user authentication details and send new device notifications.

Primary LanguagePHPMIT LicenseMIT

Package Logo

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Laravel Authentication Log is a package which tracks your user's authentication information such as login/logout time, IP, Browser, Location, etc. as well as sends out notifications via mail, slack, or sms for new devices and failed logins.

Installation

You can install the package via composer:

composer require rappasoft/laravel-authentication-log

If you want the location features you must also install torann/geoip:

composer require torann/geoip

You can publish and run the migrations with:

php artisan vendor:publish --provider="Rappasoft\LaravelAuthenticationLog\LaravelAuthenticationLogServiceProvider" --tag="authentication-log-migrations"
php artisan migrate

You can publish the view/email files with:

php artisan vendor:publish --provider="Rappasoft\LaravelAuthenticationLog\LaravelAuthenticationLogServiceProvider" --tag="authentication-log-views"

You can publish the config file with:

php artisan vendor:publish --provider="Rappasoft\LaravelAuthenticationLog\LaravelAuthenticationLogServiceProvider" --tag="authentication-log-config"

This is the contents of the published config file:

return [
    // The database table name
    // You can change this if the database keys get too long for your driver
    'table_name' => 'authentication_log',

    // The database connection where the authentication_log table resides. Leave empty to use the default
    'db_connection' => null,

    'notifications' => [
        'new-device' => [
            // Send the NewDevice notification
            'enabled' => env('NEW_DEVICE_NOTIFICATION', true),

            // Use torann/geoip to attempt to get a location
            'location' => true,

            // The Notification class to send
            'template' => \Rappasoft\LaravelAuthenticationLog\Notifications\NewDevice::class,
        ],
        'failed-login' => [
            // Send the FailedLogin notification
            'enabled' => env('FAILED_LOGIN_NOTIFICATION', false),

            // Use torann/geoip to attempt to get a location
            'location' => true,

            // The Notification class to send
            'template' => \Rappasoft\LaravelAuthenticationLog\Notifications\FailedLogin::class,
        ],
    ],

    // When the clean-up command is run, delete old logs greater than `purge` days
    // Don't schedule the clean-up command if you want to keep logs forever.
    'purge' => 365,
];

If you installed torann/geoip you should also publish that config file to set your defaults:

php artisan vendor:publish --provider="Torann\GeoIP\GeoIPServiceProvider" --tag=config

Configuration

You must add the AuthenticationLoggable and Notifiable traits to the models you want to track.

use Illuminate\Notifications\Notifiable;
use Rappasoft\LaravelAuthenticationLog\Traits\AuthenticationLoggable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable, AuthenticationLoggable;
}

The package will listen for Laravel's Login, Logout, Failed, and OtherDeviceLogout events.

Usage

Get all authentication logs for the user:

User::find(1)->authentications;

Get the user's last login information:

User::find(1)->lastLoginAt();

User::find(1)->lastSuccessfulLoginAt();

User::find(1)->lastLoginIp();

User::find(1)->lastSuccessfulLoginIp();

Get the user's previous login time & IP address (ignoring the current login):

auth()->user()->previousLoginAt();

auth()->user()->previousLoginIp();

Notifications

Notifications may be sent on the mail, nexmo, and slack channels but by default notify via email.

You may define a notifyAuthenticationLogVia method on your authenticatable models to determine which channels the notification should be delivered on:

public function notifyAuthenticationLogVia()
{
    return ['nexmo', 'mail', 'slack'];
}

You must install the Slack and Nexmo drivers to use those routes and follow their documentation on setting it up for your specific authenticatable models.

New Device Notifications

Enabled by default, they use the \Rappasoft\LaravelAuthenticationLog\Notifications\NewDevice class which can be overridden in the config file.

Failed Login Notifications

Disabled by default, they use the \Rappasoft\LaravelAuthenticationLog\Notifications\FailedLogin class which can be overridden in the config file.

Location

If the torann/geoip package is installed, it will attempt to include location information to the notifications by default.

You can turn this off within the configuration for each template.

Note: By default when working locally, no location will be recorded because it will send back the default address from the geoip config file. You can override this behavior in the email templates.

Purging Old Logs

You may clear the old authentication log records using the authentication-log:purge Artisan command:

php artisan authentication-log:purge

Records that are older than the number of days specified in the purge option in your config/authentication-log.php will be deleted.

'purge' => 365,

You can also schedule the command at an interval:

$schedule->command('authentication-log:purge')->monthly();

Displaying The Log

You can set up your own views and paginate the logs using the user relationship as normal, or if you also use my Livewire Tables plugin then here is an example table:

Note: This example uses the jenssegers/agent package which is included by default with Laravel Jetstream as well as jamesmills/laravel-timezone for displaying timezones in the users local timezone. Both are optional, modify the table to fit your needs.

<?php

namespace App\Http\Livewire;

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Jenssegers\Agent\Agent;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog as Log;

class AuthenticationLog extends DataTableComponent
{
    public string $defaultSortColumn = 'login_at';
    public string $defaultSortDirection = 'desc';
    public string $tableName = 'authentication-log-table';

    public User $user;

    public function mount(User $user)
    {
        if (! auth()->user() || ! auth()->user()->isAdmin()) {
            $this->redirectRoute('frontend.index');
        }

        $this->user = $user;
    }

    public function columns(): array
    {
        return [
            Column::make('IP Address', 'ip_address')
                ->searchable(),
            Column::make('Browser', 'user_agent')
                ->searchable()
                ->format(function($value) {
                    $agent = tap(new Agent, fn($agent) => $agent->setUserAgent($value));
                    return $agent->platform() . ' - ' . $agent->browser();
                }),
            Column::make('Location')
                ->searchable(function (Builder $query, $searchTerm) {
                    $query->orWhere('location->city', 'like', '%'.$searchTerm.'%')
                        ->orWhere('location->state', 'like', '%'.$searchTerm.'%')
                        ->orWhere('location->state_name', 'like', '%'.$searchTerm.'%')
                        ->orWhere('location->postal_code', 'like', '%'.$searchTerm.'%');
                })
                ->format(fn ($value) => $value && $value['default'] === false ? $value['city'] . ', ' . $value['state'] : '-'),
            Column::make('Login At')
                ->sortable()
                ->format(fn($value) => $value ? timezone()->convertToLocal($value) : '-'),
            Column::make('Login Successful')
                ->sortable()
                ->format(fn($value) => $value === true ? 'Yes' : 'No'),
            Column::make('Logout At')
                ->sortable()
                ->format(fn($value) => $value ? timezone()->convertToLocal($value) : '-'),
            Column::make('Cleared By User')
                ->sortable()
                ->format(fn($value) => $value === true ? 'Yes' : 'No'),
        ];
    }

    public function query(): Builder
    {
        return Log::query()
            ->where('authenticatable_type', User::class)
            ->where('authenticatable_id', $this->user->id);
    }
}
<livewire:authentication-log :user="$user" />

Example:

Example Log Table

Known Issues

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.