4

Is there a way to limit the number of sessions per user? That is, I can be logged into one machine/browsers only once.

It is essential that we limit the number of times a user can be logged in and I can't seem to find a way to do so.

Has anyone dealt with this situation?

3
  • Curious... what's the use case for this? – Brad Bell Dec 5 '14 at 18:17
  • Our site has some content that will be restricted only to our users/clients. We'd like to prevent users sharing their credentials and thus using the site in different computers at once. – cballenar Dec 5 '14 at 21:40
  • I found this question with a very specific search query on Google. I have the same use case. How did you go on about this in the end, @cballenar ? – theyve Aug 18 '20 at 12:17
5

This can be done with a plugin.

The plugin would create an 'activity' table that saves the current user's ID and a timestamp of their last known activity.

You can insert the first row by listening to the onLogin event and remove their row by listening to the onLogout.

All subsequent requests, you'd compare the last known activity timestamp to the userSessionDuration config setting to still see if they have an active session.

You'd also be listening to the onBeforeLogin event. If someone tries to login on another computer, you'd see that they already have a row in the database and their last known activity timestamp is still in a valid userSessionDuration window. Then you'd pass $performAction = false as a property of the event, and that will prevent the login action from occurring.

2
  • Thanks for all the details, Brad! I haven't worked on plugins yet but I guess I had to start at some point. – cballenar Dec 5 '14 at 23:52
  • 1
    Does that mean that by default Craft allows unlimited sessions per user? I'm curious as it is something I intend to use - giving one temporary login to a group of students to save time over creating many accounts. – Adam Menczykowski Mar 1 '16 at 15:01
3

Im on Craft v3.4.25, and I've added the following event handler in my module to handle this. There are two scenarios here: either you want the new login to invalidate any existing session, or you want to block the new login if an active session exists.

If you want the new session to succeed, and log out any other sessions for that same user, you can do this:

Event::on(
    \yii\web\User::class,
    \yii\web\User::EVENT_BEFORE_LOGIN,
    function (UserEvent $event) {
        // remove all existing sessions for this user
        Session::deleteAll(['userId' => $event->identity->id]);
        return;
    }
);

This will remove all existing sessions for that user before logging them in with a new session.

If, instead, you want to block the user from logging in if another active session already exists, you could try this other method:

Event::on(
    \yii\web\User::class,
    \yii\web\User::EVENT_BEFORE_LOGIN,
    function (UserEvent $event) {
        // if this is not a CP request...
        if(!Craft::$app->getRequest()->getIsCpRequest()){
            $session = Session::find($event->identity->id)
                ->orderBy('dateUpdated desc')
                ->one();
            // if there is a session, and it was active within the last 'userSessionDuration' seconds...
            // do not let user login (only one user login at a time allowed)
            if($session && DateTimeHelper::isWithinLast($session->dateUpdated, $event->duration.' seconds')){
                $event->isValid = false;
                // temporarily override the login failure message for this request
                Event::on(
                    UsersController::class,
                    UsersController::EVENT_LOGIN_FAILURE,
                    function (LoginFailureEvent $event) {
                        $event->message = "This account is already logged in at another location.";
                    }
                );
                return;
            }
        }
    }
);

The UserEvent has a variable $duration which I believe is set by the general config variable userSessionDuration, which is in seconds.

For non-CP requests, this method should only allow a single login for a user account, at one time.

I added another event handler on UsersController::EVENT_LOGIN_FAILURE to set a custom error message when a login fails due to another active session for that user.

Seems to be working so far... hope this helps someone else. Feel free to poke holes!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.