DEV Community

trungpv
trungpv

Posted on

Laravel: How to tracking user IP after they register or login

Brainstorm

The plan is create a new column in the user table to store the user country code as the source country that user sign up from and the table to store the history of the user ip address for every day or every time user they login into the system.

We will have 2 events to trigger and store the information

  • After user registration
  • After user login and when user visit the backend for the normal user (Caching to too many action when they access the page)

Research

Now, We need to find some libs to help us do this stuff 😎. Because, I'm kind of lazy guy. Just want to take advance and help my productive.

Searching google.com and github.com then I found few libs to help us. But I saw that this one clean and the last commit just 19 days ago which mean It is good support from the creator.

https://github.com/stevebauman/location

https://github.com/stevebauman/location

Let's go

1. Play around

composer require stevebauman/location
Enter fullscreen mode Exit fullscreen mode

Create a route to test the data first before we go deep into it

use Stevebauman\Location\Facades\Location;

Route::get('/_t', function () {
    if ($position = Location::get()) {
        // Successfully retrieved position.
        return $position;
    } else {
        // Failed retrieving position.
        return ':(';
    }
})->name('test');
Enter fullscreen mode Exit fullscreen mode

Result:

{
  "ip": "66.102.0.0",
  "countryName": "United States",
  "countryCode": "US",
  "regionCode": "CA",
  "regionName": "California",
  "cityName": "Mountain View",
  "zipCode": "94043",
  "isoCode": null,
  "postalCode": null,
  "latitude": "37.422",
  "longitude": "-122.084",
  "metroCode": null,
  "areaCode": "CA",
  "timezone": "America/Los_Angeles",
  "driver": "Stevebauman\\Location\\Drivers\\IpApi"
}
Enter fullscreen mode Exit fullscreen mode

As you can see, We will base on the data above to create the migration in the Laravel project.

2. Migration

Create country_code column to the users table

php artisan make:migration add_country_code_column_to_users_table
Enter fullscreen mode Exit fullscreen mode
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddCountryCodeColumnToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('country_code')->nullable()->index();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('country_code');
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Create a history table to store user IP address

php artisan make:model UserIpAddress -m
Enter fullscreen mode Exit fullscreen mode
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserIpAddressesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('user_ip_addresses', function (Blueprint $table) {
            $table->id();
            $table->string('ip')->index(); // "66.102.0.0",
            $table->string("countryName")->nullable(); // "United States",
            $table->string("countryCode")->nullable(); // "US",
            $table->string("regionCode")->nullable(); // "CA",
            $table->string("regionName")->nullable(); // "California",
            $table->string("cityName")->nullable(); // "Mountain View",
            $table->string("zipCode")->nullable(); // "94043",
            $table->string("isoCode")->nullable(); // null,
            $table->string("postalCode")->nullable(); // null,
            $table->string("latitude")->nullable(); // "37.422",
            $table->string("longitude")->nullable(); // "-122.084",
            $table->string("metroCode")->nullable(); // null,
            $table->string("areaCode")->nullable(); // "CA",
            $table->string("timezone")->nullable(); // "America/Los_Angeles",
            $table->char('user_id', 26)->index();
            $table->timestamps();

            $table->index(['ip', 'user_id']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_ip_addresses');
    }
}
Enter fullscreen mode Exit fullscreen mode
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class UserIpAddress extends Model
{
    use HasFactory;

    protected $guarded = [];

    public function parseData($data)
    {
        $payload = [];

        $payload["countryName"] = $data["countryName"] ?? false;
        $payload["countryCode"] = $data["countryCode"] ?? false;
        $payload["regionCode"] = $data["regionCode"] ?? false;
        $payload["regionName"] = $data["regionName"] ?? false;
        $payload["cityName"] = $data["cityName"] ?? false;
        $payload["zipCode"] = $data["zipCode"] ?? false;
        $payload["isoCode"] = $data["isoCode"] ?? false;
        $payload["postalCode"] = $data["postalCode"] ?? false;
        $payload["latitude"] = $data["latitude"] ?? false;
        $payload["longitude"] = $data["longitude"] ?? false;
        $payload["metroCode"] = $data["metroCode"] ?? false;
        $payload["areaCode"] = $data["areaCode"] ?? false;
        $payload["timezone"] = $data["timezone"] ?? false;

        return $payload;
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Store Information with Laravel Events

After few years, I working with Laravel. The one I loved that the queue and especially that the Laravel Horizon is very powerful when we process parallel the tasks and jobs at the same time.

If you do not have experience about the Laravel Queue. It is a mistake when you working with the Laravel stack 🌝

So, We go into the Laravel Events. I love Laravel document. They made it very useful, clean and clear. πŸŽ‰

After I research I found 2 events we can use it which are Illuminate\Auth\Events\Registered, Illuminate\Auth\Events\Login. You can check it out in app/Providers/EventServiceProvider.php file

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [

        ],
        Login::class => [

        ]
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {

    }
}
Enter fullscreen mode Exit fullscreen mode

So now, I will create 2 listeners to listen those event to trigger my code and store information.

Let’s read the Laravel event document to learn how to create the listener.

php artisan make:listener LogUserIpAddressLoginListener
php artisan make:listener LogUserIpAddressRegisteredListener
Enter fullscreen mode Exit fullscreen mode
<?php

namespace App\Listeners;

use App\Models\UserIpAddress;
use Illuminate\Auth\Events\Login;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Stevebauman\Location\Facades\Location;

class LogUserIpAddressLoginListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  \App\Events\Login  $event
     * @return void
     */
    public function handle(Login $event)
    {
        $user = $event->user;
        if ($user) {
            if ($position = Location::get()) {
                $userIpAddress = new UserIpAddress(
                    UserIpAddress::parseData((array) $position)
                );
                $userIpAddress->ip = $position->ip;
                $userIpAddress->user_id = $user->id;
                $userIpAddress->save();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Stevebauman\Location\Facades\Location;

class LogUserIpAddressRegisteredListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  \App\Events\Registered  $event
     * @return void
     */
    public function handle(Registered $event)
    {
        $user = $event->user;
        if ($user) {
            if ($position = Location::get()) {
                $user->country_code = $position->countryCode;
                $user->save();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we add 2 listeners into the EventServiceProvider

<?php

namespace App\Providers;

use App\Listeners\LogUserIpAddressLoginListener;
use App\Listeners\LogUserIpAddressRegisteredListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Auth\Events\Login;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            LogUserIpAddressRegisteredListener::class
        ],
        Login::class => [
            LogUserIpAddressLoginListener::class
        ]
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {

    }
}
Enter fullscreen mode Exit fullscreen mode

Done πŸŽ‰

If you want to make it faster and cooler that you can use the Queue (Because for the site which have big traffic and huge users that we can process it later and do not stress on the server). By implements the Illuminate\Contracts\Queue\ShouldQueue for those listeners. That’s why I <3 Laravel.

Enjoy the result.

Thanks for reading πŸ‘‹

Top comments (0)