Limit one session per user in Laravel 5

Laravel 5 does not limit the number of sessions a user may have open, so the web application may be open in several browsers of several computers.

In a project in which I am participating we need that the user can only have an active session, so if it is authenticated in a browser, the rest of sessions have to disappear, so the user can not use the web application in the other browsers, except in the last one. The example is developed with Laravel 5.4.

The first thing I will do is add a new migration to add a text field to the “users” table, where I will store the id of the active session.

$ php artisan make:migration add_session_id_to_users_table --table=users

Inside the migration file I add the new column:

<?php

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

class AddSessionIdToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->text('session_id')
                ->after('remember_token')
                ->nullable()
                ->default(null)
                ->comment('Stores the id of the user session');
        });
    }

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

Then I execute the migration:

$ php artisan migrate

The next thing is to control this session in the response method after user authentication. To do this, I have to overwrite the method “sendLoginResponse” of the trait “AuthenticatesUsers” which is in the namespace “Illuminate\Foundation\Auth”. The original method is:

/**
 * Send the response after the user was authenticated.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
protected function sendLoginResponse(Request $request)
{
    $request->session()->regenerate();

    $this->clearLoginAttempts($request);

    return $this->authenticated($request, $this->guard()->user())
            ?: redirect()->intended($this->redirectPath());
}

This trait is used in the controller “LoginController” which is in the namespace “App\Http\Controllers\Auth”, so here is where I’m going to overwrite it, since that is where the answer is issued after authenticating the user.

I add the following references in the header:

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

And then the new method:

/**
 * Send the response after the user was authenticated.
 * Remove the other sessions of this user
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
protected function sendLoginResponse(Request $request)
{
    $request->session()->regenerate();
    $previous_session = Auth::User()->session_id;
    if ($previous_session) {
        Session::getHandler()->destroy($previous_session);
    }

    Auth::user()->session_id = Session::getId();
    Auth::user()->save();
    $this->clearLoginAttempts($request);

    return $this->authenticated($request, $this->guard()->user())
        ?: redirect()->intended($this->redirectPath());
}

In this method, what I do besides the original method that I overwrite is the following:

$previous_session = Auth::User()->session_id;
if ($previous_session) {
    Session::getHandler()->destroy($previous_session);
}

Here I get the id of the user session from the database. If it exists, I destroy the session.

Auth::user()->session_id = Session::getId();
Auth::user()->save();

Next, I update the session id in the user, I persist in the database this information and continue the cycle of the response.

Thus, the user can only have one session open in a single device.

Leave a Reply

Your email address will not be published. Required fields are marked *