Bervetuna's avatar

Jetstream: Log out on other devices on log in

I'm working on an app and would like to prevent users to be logged in on multiple devices.

The documentation mentions this can be done by uncommenting the Illuminate\Session\Middleware\AuthenticateSession middleware in App\Http\Kernel and then use the logoutOtherDevices method provided by the Auth facade.

I tried so by adding the following code in the FortifyServiceProvider boot method:

Fortify::authenticateUsing(function (Request $request) { $user = User::where('email', $request->email)->first();

    if ($user &&
        Hash::check($request->password, $user->password)) {
        Auth::logoutOtherDevices($request->password);
        return $user;
        
    }
});

I was hoping that calling the logoutOtherDevices method before returning the user would logout the user from other sessions. That is not the case.

What am I doing wrong here?

Thanks!

0 likes
17 replies
fylzero's avatar

@bervetuna Try this (untested)...

art make:listener LogoutOtherUsersListener

app/Providers/EventServiceProvider.php

protected $listen = [
    Login::class => [LogoutOtherUsersListener::class],
];

app/Listeners/LogoutOtherUsersListener.php

use Illuminate\Support\Facades\Auth; // Up at the top

public function handle($event)
{
    Auth::logoutOtherDevices($event->user->password);
}
Bervetuna's avatar

@fylzero Thanks for the suggestion!

Unfortunately, I still can log in on 2 different browsers.

fylzero's avatar

@Bervetuna shouldn't stop you from logging in elsewhere, but it should log you out of other sessions. That's what you wanted, right?

Bervetuna's avatar

@fylzero Indeed.

I would like to prevent users from sharing their credentials with someone else. So if someone logs in, other sessions (with the same credentials) should be deleted.

I can now log in with for example Google Chrome, then log in with Firefox, and still refresh the google chrome session without being logged out.

1 like
Bervetuna's avatar

@Snapey Meaning not checked the remember me checkbox? Or is there a setting to disable the option 'remember me'?

fylzero's avatar
fylzero
Best Answer
Level 67

@Bervetuna I could be wrong but from what I remember, authed sessions I believe are based on an auth identifier that is based on your ip address. Try logging in from you phone - not on wifi. It should accomplish what you are trying. Do you really care if someone is logged in from 2 browsers on the same machine?

Has to be a different device - thus logoutOtherDevices

This actually may be able to exist on the same wifi or network. I can't remember what the identifier is. ...but honestly what was posted should be sufficient to do what you're ultimately wanting. Test this using two different devices on different networks. Building anything more complex than that seems highly unnecessary.

1 like
Bervetuna's avatar

@fylzero I deployed the project to a webserver (until no, it was on my local machine) and indeed: login in from my phone (not on wifi) deleted the session on my laptop!

Thanks a lot!

1 like
Bervetuna's avatar

I misspelled the import of use Illuminate\Auth\Events\Login in the EventServiceProvider.php (used lowercase for login).

No that I corrected that, an error is thrown when trying to login: InvalidArgumentException The given password does not match the current password.

Snapey's avatar

@Bervetuna change this to use the user's password not the request password?

    if ($user &&
        Hash::check($request->password, $user->password)) {
        Auth::logoutOtherDevices($user->password);
        return $user;
        
    }
Bervetuna's avatar

@Snapey vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php:672

/** * Rehash the current user's password. * * @param string $password * @param string $attribute * @return \Illuminate\Contracts\Auth\Authenticatable|null * * @throws \InvalidArgumentException */ protected function rehashUserPassword($password, $attribute) { if (! Hash::check($password, $this->user()->{$attribute})) { throw new InvalidArgumentException('The given password does not match the current password.'); }

    return tap($this->user()->forceFill([
        $attribute => Hash::make($password),
    ]))->save();
}
Bervetuna's avatar

@Snapey In stead of working with a listener, that is?

I changed the method in the FortifyServiceProvider to:

Fortify::authenticateUsing(function (Request $request) { $user = User::where('email', $request->email)->first();

        if ($user &&
        Hash::check($request->password, $user->password)) {
        Auth::logoutOtherDevices($user->password);
        return $user;
        
    }
    });

Still able to run multiple sessions simultaneously.

Snapey's avatar

@Bervetuna Show your web middleware

ps, and please, format your code blocks by putting three backticks ``` on their own line before and after the code blocks

1 like
Bervetuna's avatar

@Snapey

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
Snapey's avatar

@Bervetuna I suppose you just uncommented the line, because the docs uses:

Illuminate\Session\Middleware\AuthenticateSession::class

As a test, can you login on both browsers, check that you can use both, then change your password in one of them?

Please or to participate in this conversation.