18
votes

I'm trying to implement a user policy whereby only one user can login at a time. I'm trying to build this on top of Laravel's Auth driver.

I've thought of using the Session driver to store the sessions in the database and make the keys constant for each username. This is probably a terrible implementation because of session fixation.

What would the implementation be like? What methods in the Auth driver should I be editing? Where would the common session key be stored?

6

6 Answers

25
votes

I recently did this.

My solution was to set the session value when a user logs in. Then I had a small class checking if the session ID stored is the same as the current user who is logged in.

If the user logs in from somewhere else the session ID in the DB will update and the "older" user will be logged out.

I didn't alter the Auth driver or anything, just put it on top when the user logs in. Below happens when login is successful:

$user->last_session = session_id();
$user->save();

To check if the session is valid I used below

if(session_id() != Auth::user()->last_session){
   Auth::logout();
   return true;
}

As you can see I added a column in the users table called last_session

14
votes

With Laravel 5.6 and superior:

in LoginController add method

protected function authenticated()
{
    \Auth::logoutOtherDevices(request('password'));
}

in Kernel

remove comment from line

\Illuminate\Session\Middleware\AuthenticateSession::class,

That's it, the feature is now included in Laravel!

10
votes

What i did in Laravel 5.7 is similar to Somwang's solution, however this will keep the newly logged in user active and logout the old user.

Start with creating a middleware class:

php artisan make:middleware CheckSingleSession

Add the following:

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

And write out the handle like this:

public function handle($request, Closure $next)
{

    $previous_session = Auth::User()->session_id;
    if ($previous_session !== Session::getId()) {

        Session::getHandler()->destroy($previous_session);

        $request->session()->regenerate();
        Auth::user()->session_id = Session::getId();

        Auth::user()->save();
    }
    return $next($request);
}

Make sure you also add it in your users migration file:

Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('firstName');
    $table->string('lastName');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->string('session_id')->nullable();
    $table->rememberToken();
    $table->timestamps();
});

Then you should be able to add it to your routeMiddleware in the Kernel: project\app\Http\Kernel.php

'checksinglesession' => \App\Http\Middleware\CheckSingleSession::class,

(make sure you check the comma's)

And we are done, now you can use it as route middleware. This way there is an active check on single sessions.

Route::group(['middleware' => ['auth' ,'checksinglesession'], 'prefix' => 'app'], function () {
    Route::get('/dashboard', 'DashboardController@index')->name('dashboard.index');
});
3
votes

my solution was extended from @Albin N for Laravel 5.* onward

add "last_session" column into table users

make sure you allow this column is fill-able by adding "last_session" into $fillable on User model (User.php)

protected $fillable = [
    'name', 'email', 'password','last_session'
];

add authenticated() function into App/Http/Controllers/Auth/LoginController.php if you can't find it just make sure you have run php artisan make:auth

protected function authenticated()
{
    // Update last_session after logged-in
    User::find(Auth::id())->update(['last_session'=>Session::getId()]);
}

create new middleware class php artisan make:middleware SingleSession

if(Auth::check())
{
   // If current session id is not same with last_session column
   if(Auth::user()->last_session != Session::getId())
   {
      // do logout
      Auth::logout();

      // Redirecto login page
     return Redirect::to('login');
   }
}

finally call you SingleSession middleware class in kernel.php

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\SingleSession::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

it will check every time before routes are being executed that's it! happy coding..!

1
votes

This question is much earlier, but someone will be benefited through these easy steps. No need to make extra migration or something else.

The first step is to comment out the:

\Illuminate\Session\Middleware\AuthenticateSession::class, line from App\Http Kernel.php class.

Secondly add this line in your login function just after login attempt successfully and before redirection: \Auth::logoutOtherDevices(request('password'));

Thanks.

0
votes

Here’s how you can accomplish this. First of all, you need to uncomment the line \Illuminate\Session\Middleware\AuthenticateSession::class from $middlewareGroups property in app/Http/Kernel.php file because this is the middleware that manages the user sessions in Laravel.

2- add this line in your login function just after login attempt successfully and before redirection: \Auth::logoutOtherDevices(request('password'));

use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($password);

   if(Auth::guard('student')->attempt([],$request->rememberme))
    {

        Auth::guard('student')->logoutOtherDevices(request('password'));

        return redirect()->intended('/');  //route('homepage');
    }

https://www.amitmerchant.com/logout-from-everywhere-except-current-device-laravel/

demo https://xpredo.com