Anonymized Website Visitor Counter in Laravel

Anonymized Website Visitor Counter in Laravel

Building a middleware to count unique visitor count in your Laravel application

In this day and age, we all need to have some sort of idea of our website's performance and the least we can do for this is to somehow track our audience - the visitors of our webpage. This can be done by using a third-party service like Google Analytics, Cloudflare or other services. These, unfortunately, expose our visitors to cookies and come in with a whole set of challenges pertaining to GDPR and other privacy-regulating laws in the EU and also other countries of the world. Thought this approach might be the easiest one when it comes to implementing such functionality into your website, you might have to deal with the legal side of things (like privacy policy, cookie consent, and more) on top of it. If you are already using cookies in some capacity other than for example session cookies or XSFR tokens (like in basic Laravel apps), you may not have a problem with this, but if you want to be free of these third-party cookies, I've got another option for you.

The Concept

The concept of this approach to visitor tracking is fairly simple. The goal is to have an anonymized record of unique visits of our website for each day. I'm going to show an example of this in Laravel, but this concept can be taken to other frameworks and languages. I've tested this in regular PHP, it can be also done in .NET or Python (with some modifications of course).

The main thing that makes this method more privacy-compliant than using those third-party tools is anonymization. Almost no data about the user is being collected on the server-side. We only collect the date of the visit, and the visitor's IP address which is then hashed and only the hash is stored in the database.

The Execution

As simple as the concept was, the execution is keeping it simple as well. For this to work, we are going to need to write a custom middleware which determines if a visitor has already been recorded for a given day. If the visitor hasn't been recorded, it will insert their visit into the database, otherwise, the visit will not be registered. The middleware checks the visitor's IP hash against the hash in the database to determine if the visit had been recorded before. Now let's look at the code of this middleware:

// app/Http/Middleware/CountVisitor.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\Visitor;

class CountVisitor
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $ip = hash('sha512', $request->ip());
        if (Visitor::where('date', today())->where('ip', $ip)->count() < 1)
        {
            Visitor::create([
                'date' => today(),
                'ip' => $ip,
            ]);
        }
        return $next($request);
    }
}

This one is pretty straight forward and does exactly what has already been described at the start of this part of the article. We are only inserting today's date and SHA-512 hashed IP address of the visitor into the database. You could hash it with another hashing algorithm like bcrypt or SHA-256, but I went for SHA-512 in this case, though they all are fairly safe ways to hash the data.

Of course, the middleware also needs to be registered in app/Http/Kernel as a route middleware. I've registered it like this:

// app/Http/Kernel.php

...
protected $routeMiddleware = [
    ...,
    'visitor' => \App\Http\Middleware\CountVisitor::class
];

Now, you can add this to your web routes as a middleware. Alternatively, you might want to apply it to all web routes, in that case, you would register this middleware in the web middleware group in app/Http/Kernel. I usually don't want this to fire up on administrative routes though, so I use this middleware only for publicly accessible pages. The routes then looks like this:

// routes/web.php

<?php

use Illuminate\Support\Facades\Route;

Route::name('front.')->middleware('visitor')->group(function() {
    Route::get('/', 'FrontpageController@index')->name('index');
    ...
});

Visualizing the collected data

For this article, I'm not going to go into detail about visualizing the data we've collected as that could be a whole separate article. I'm just going to leave a link to a GitHub gist containing the code needed to visualize the data. The final result can be seen below.

hashnode_visits.png

Here is the source

The pros and cons

This method has several advantages and disadvantages, let's explore them here.

The pros

  • Anonymized data
  • Simple solution
  • Simple implementation
  • First party
  • No cookies

The cons

  • The collected data doesn't reflect the visitor's behavior on the website (e.g. time spent on the webpage, visited pages, etc.)
  • You have to write your own front-end to visualize the data
  • The database size might get quite big if you have a lot of traffic and the queries might get slow over time

These are more or less the biggest advantages and disadvantages of using this approach.

Wrapping up

This has been a showcase of a simple way of counting your website visitation by using a Laravel middleware. Note that this has only been tested on applications with about 50 unique visitors per day which I consider a small sample size. I am not very sure how viable would this approach be for a bigger project, but if you want to give it a fair try, go right ahead.

Cover photo by Carlos Muza on Unsplash

Did you find this article valuable?

Support Dominik Zarsky by becoming a sponsor. Any amount is appreciated!