Laravel :: How I deny simultaneous access to multiple users

This post was published originally on medium.com/@eichgi/laravel-how-i-deny-simul.. for my old blog.

Hi fellows devs, today I write about a problem I faced with a current saas project I am working on. As the title suggest I am using Laravel and I will tell you about the problem and the solution I implemented. I don’t know if it’s the best (which I believe not) but so far it’s solved.

Problem: How allow only one user to be logged in one device?

Solution: Creating a middleware and validating the session the last user created.

For this scenario I created a migration called session_tokens which is as follows:

Schema::create('session_tokens', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('user_id');
    $table->string('token');
    $table->timestamps();
});

Here we are going to generate and save a token every time a user logs in, this token will be store in the user’s session as well. Laravel provides several mechanisms and one of them are Service Providers. We need to register a listener for Login Event on the EventServiceProvider.php:

protected $listen = [
    'Illuminate\Auth\Events\Login' => [
        'App\Listeners\CreateSessionToken',
    ],
];

As you can see we are leveraging from events which are already triggered by Laravel Auth. Then we have to run the appropiate command for creating listeners which is:

php artisan event:generate

We will find a new event called CreateSessionToken.php at App/Listeners folder. We need to modified the handle method as follows:

class CreateSessionToken
{
    public function __construct()
    {
        //
    }

    public function handle(Login $event)
    {
        $token = new SessionToken;
        $token->user_id = $event->user->id;
        $token->token = str_random(16);
        $token->save();
        session(['token' => $token->token]);
    }
}

Now that we have created and stored the token in our database and session we can create a middleware for validating every request we wanted to be validated, so if the current user has the last token registered it means the request could be performed, otherwise the user has to be logged out because another one registered another session token.

So we need to run the next command for creating a middleware:

php artisan make:middleware ValidateSessionToken

We will find a new file called ValidateSessionToken.php in /App/Http/Middleware folder and it has to be modified as follows:

class ValidateSessionToken
{
    public function handle($request, Closure $next)
    {
        $token = SessionToken::where('user_id', Auth::id())->orderBy('created_at', 'DESC')->first()->token;
        if ($request->session()->get('token') != $token) {
            return redirect('/logout');
        }

        return $next($request);
    }
}

As you can see we retrieve the last token generated by the user which is performing the request and it is compared with the session token, if fails user is logged out. Now we only need to apply this middleware and we have basically 3 options: middlewares, group middlewares and route middlewares.

I defined it as a route middleware:

protected $routeMiddleware = [
    ...
    'onlyOneUser' => \App\Http\Middleware\ValidateSessionToken::class,
];

and that’s because I wanted to have the freedom to apply it partially and not in every request that has been performed. But you can choose which one is the best option.

Also I have to mention, if you are going to apply to the common middleware option you need to ensure you also have the middleware for authentication and sessions because as you have noticed you need them for validating the request because we are using the Auth facade and the session from the request.

And finally this is how I define when to apply the middleware by calling it from the constructor of every controller I wanted to be validated.

public function __construct()
{
    ...
    $this->middleware('onlyOneUser');
}

Probably there will be a better option because I think it’s quite expensive to perform a query for every request the middleware is called, please let me know if you have already solved it a better way! and if not I hope this can helps you!

No Comments Yet