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.

 

 

4 comments

  1. Hola gran aporte, mi pregunta es como se podria dar limitar las sessiones a mayor numero, por ejemplo a 4 sessiones.

  2. Se me ocurre lo siguiente:
    – En vez de tener un campo “session_id” en la tabla “users”, crea una nueva tabla de “sessions”, con una relación 1-n con la tabla de “users”. Esa tabla “sessions” tendría que tener un “id”, un “user_id” y un “session_id”.
    – En esa tabla “sessions” vas guardando las sesiones para cada usuario.
    – En el método sendLoginResponse() compruebas si hay 4 o más sesiones para el usuario que se está logueando y destruyes las correspondientes si sucede eso. Luego añades tu sesión a la tabla.

    Creo que de esta forma incluso podrías introducir el límite como un parámetro en el fichero .env y ser variable.

  3. Hola! Agradezco tu tiempo, empiezo a experimentar con Laravel, hice todo pero sigo teniendo mas de 1 sesion abiertas, hay algo mas que halla que implementar?

    Gracias

  4. Hola
    Tal y como está explicado en esta entrada funciona, ya que está probado en un proyecto real en producción. Lo que estás haciendo es borrar sesiones previas a la que estás creando al loguearte. Otra aproximación que se me ocurre, más costosa, es hacerlo en un middleware. Es más costosa porque ejecutas el código en cada petición y no solo en el login. Prueba así y me cuentas.

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.