Warm tip: This article is reproduced from serverfault.com, please click

Invalidating Session not logging out

发布于 2020-11-26 23:53:38

So basically a user can stay logged in accross iPhone, Chrome, Firefox and multiple browsers and I want to stop that, and only allow the user to be logged in at (1 session only at a time).

Meaning: When the user logs in somehwere else... it logs them out on the other.

I have added the following to the very bottom of my LoginController.php

/**
 * The user has been authenticated.
 *
 * 
 * @return mixed
 */
 
 
protected function authenticated()
{
    \Auth::logoutOtherDevices(request($password));
}

I also uncommented the line: \Illuminate\Session\Middleware\AuthenticateSession::class, in my Kernel.php

But it still allows the user to stay logged in across many browsers.

I would like it to invalidate the session correctly and log the user OUT everywhere else wherever it is logged in.

Here is my complete LoginController.php just incase I have made some mistake:

   <?php

            namespace App\Http\Controllers\Auth;

            use App\Http\Controllers\Controller;
            use Illuminate\Foundation\Auth\AuthenticatesUsers;


            class LoginController extends Controller
            {
                /*
                |--------------------------------------------------------------------------
                | Login Controller
                |--------------------------------------------------------------------------
                |
                | This controller handles authenticating users for the application and
                | redirecting them to your home screen. The controller uses a trait
                | to conveniently provide its functionality to your applications.
                |
                */

                use AuthenticatesUsers;

                /**
                 * Where to redirect users after login.
                 *
                 * @var string
                 */
                protected $redirectTo = '/dashboard';

                /**
                 * Create a new controller instance.
                 *
                 * @return void
                 */
                public function __construct()
                {

                    $this->middleware('guest')->except('logout');

                    $this->middleware('guest:admin', ['except' => 'logout']);

                }

                public function username()
                {
                    return 'username';
                }


                public function logoutUser()
                {

                    $this->guard()->logout();

                    $this->session()->invalidate();

                    return redirect('/login');
                }
                
            }



            /**
                 * The user has been authenticated.
                 *
                 * 
                 * @return mixed
                 */
                 
                 
                protected function authenticated()
                {
                    \Auth::logoutOtherDevices(request($password));
                }
Questioner
Merky
Viewed
0
bhucho 2020-11-28 18:23:31

SO, basically this is a custom logoutotherdevices which I have made with the help of middleware.

Basically the idea is to pass active_key value as random string and keep it in user table for that user as well as store it in session, we need to create new value at time of registration,update the column value for every user login and set it to null on logout, so if in not matches with the value stored in session, we would logout the user from that session.

Step 1:

  • Create the table column which stores random string in user table.

For that I have added a new column in user table(App\User Model), named active_key.

$table->string('active_key')->nullable();

Added a column in migration. Then migrate it using php artisan migrate. NOTE: If you don't want to create a new column you use the remember_token column key though I will suggest not to use it,as it is originally used for some other purpose. IMP : At the end, add active_key to $fillable array in User Model

Step 2:

Note in your Auth\LoginController, we would override the authenticated and logout method, Just as the class Auth\LoginController begins, we can see trait included as use AuthenticatesUsers, I have changed it to

use AuthenticatesUsers{
        logout as logoutFromAuthenticatesUsers;
    }

so to change the default logout() function name defined in trait AuthenticatesUsers to logoutFromAuthenticatesUsers() and then I have used it in logout function as seen below.

protected function authenticated(Request $request, $user)
    {
        $this->generateAndUpdateActiveKey($request, $user);
        
        return redirect()->intended($this->redirectPath());
    }
public function logout(Request $request)
    {
        $user = \Auth::user();
        if(!$user->update(['active_key' => null])){
            throw new \Illuminate\Database\Eloquent\ModelNotFoundException();
        }
        if($request->session()->has('active_key')){
            $request->session()->forget('active_key');
        }

        $this->logoutFromAuthenticatesUsers($request);
        return redirect()->intended($this->redirectPath());
    }

What it will do is authenticated method will be called after the user is authenticated successfully, then the generateAndUpdateActiveKey() will update the active_key column in the user table and store the same value in session.

generateAndUpdateActiveKey() is defined in App\Http\Controller to avoid redundancy.
public function generateAndUpdateActiveKey(\Illuminate\Http\Request $request, \App\User $user){

        $activeKey = substr(md5(time().\Auth::user()->name),0,16);
        $request->session()->put('active_key', $activeKey);
        // $request->session()->put('loggedin_ip',$request->ip());

        if(!$user->update(['active_key' => $activeKey])){
            throw new \Illuminate\Database\Eloquent\ModelNotFoundException();
        }
    }

In case of logout, we override it from the trait AuthenticatesUsers used to delete the active_key value for the login user from the users table and then remove it from session then call the logout function.

To create new active_key value when a user registers

For that I have overridden the registered() method from trait RegistersUsers( added as use RegistersUsers;) So in, Auth\RegisterController.php

protected function registered(Request $request, $user)
    {
        $this->generateAndUpdateActiveKey($request, $user); // same method defined in controller
        
        return redirect()->intended($this->redirectPath());
    }

Step 3:

With all set up we now just need to check that every time the page is refreshed, the current session is the only session for that user. For that we use a middleware, I have named it ActiveLogin. php artisan make:middleware ActiveLogin will create a basic layout of middleware in App\Http\Middleware -> ActiveLogin.php. Register the middleware in App\Http\kernel.php, I have added it as route middleware, you can even add it along the group of web middleware to check as group for each routes.

 protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        
           .....
 
        'activelogin' => \App\Http\Middleware\ActiveLogin::class,
    ];

We will change the ActiveLogin Middleware as

<?php

namespace App\Http\Middleware;

use Closure;

class ActiveLogin
{
    /**
     * Handle an incoming request to check the current user is logged in current device.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ( \Auth::check() ){
            if ($request->session()->has('active_key')){
                if(strcmp(\Auth::user()->active_key, $request->session()->get('active_key')) !== 0){
                    \Auth::logout();
                    return redirect()->to('/')->with(['warning' => 'Your session has expired because your account is logged in somewhere else.']);
                }
            }
        }
        return $next($request);
    }
}

Then you can use it in your routes web.php

Route::group(['middleware' => ['activelogin']], function(){
    Route::get('/', function () {
        return view('welcome');
    });
    Route::get('/home', 'HomeController@index')->name('home');
        
});

You can show your warning in welcome.blade.php as

@if(session()->has('warning'))
                    <div class="alert alert-warning" role="alert"> 
                    {{ Session::get('warning') }}
                    </div>
                @endif

Note: If you add it in route group of web then it will be applied automatically to all the routes in web.php .


Demo

This is the first login of user try2 from separate session(incognito mode), enter image description here This is the second login of user try2 from separate session(normal mode), enter image description here Once the same user logs in from separate session(normal mode), if user tries to go to some other link or refeshes page from the first session(in incognito), user is logged out from first session with warning message as shown in welcome page picture below enter image description here