Limitar a una sesión por usuario en Laravel 5

Laravel 5 no limita el número de sesiones que puede tener abiertas un usuario, por lo que puede tener abierta la aplicación web en varios navegadores de varios equipos.

En un proyecto en el que estoy participando necesitamos que el usuario solo pueda tener una sesión activa, con lo que si se autentifica en un navegador, el resto de sesiones tienen que desaparecer, con lo cual no podrá usar la aplicación web en el resto de navegadores, excepto en el último. El ejemplo está desarrollado con Laravel 5.4.

Lo primero que voy a hacer es añadir una nueva migración para añadir un campo de texto en la tabla de usuario, donde voy a almacenar el id de la sesión activa.

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

Dentro del archivo de la migración añado la nueva columna:

<?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('Almacena el id de la sesión del usuario');
        });
    }

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

Y ejecuto la migración:

$ php artisan migrate

Lo siguiente es controlar esta sesión en el método de respuesta tras la autentificación del usuario. Para ello tengo que sobrescribir el método “sendLoginResponse” del trait “AuthenticatesUsers” que se encuentra en el namespace “Illuminate\Foundation\Auth”. El método original es:

/**
 * 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());
}

Este trait es usado en el controlador “LoginController” que se encuentra en el namespace “App\Http\Controllers\Auth”, por lo que es aquí donde lo voy a sobrescribir, ya que es donde precisamente se emite la respuesta tras autenticar al usuario.

Añado en la cabecera las siguentes referencias:

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

Y luego el nuevo método:

/**
 * Envía la respuesta después de que el usuario se autentifique.
 * Elimina el resto de sesiones de este usuario
 *
 * @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());
}

En este método lo que hago a mayores con respecto al método original que sobrescribo es lo siguiente:

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

Aquí obtengo el id de la sesión del usuario de la base de datos. En caso de que exista destruyo la sesión.

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

A continuación actualizo el id de sesión en el usuario, lo persisto en la base de datos y continuo el ciclo de la respuesta.

De esta forma el usuario solo puede tener una sesión abierta en un único dispositivo.

 

 

Leave a Reply

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