<p>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.</p>
<p>If you want the new session to succeed, and log out any other sessions for that same user, you can do this:</p>
<pre><code>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' =&gt; $event-&gt;identity-&gt;id]);
        return;
    }
);
</code></pre>
<p>This will remove all existing sessions for that user before logging them in with a new session.</p>
<p>If, instead, you want to block the user from logging in if another active session already exists, you could try this other method:</p>
<pre><code>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-&gt;getRequest()-&gt;getIsCpRequest()){
            $session = Session::find($event-&gt;identity-&gt;id)
                -&gt;orderBy('dateUpdated desc')
                -&gt;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 &amp;&amp; DateTimeHelper::isWithinLast($session-&gt;dateUpdated, $event-&gt;duration.' seconds')){
                $event-&gt;isValid = false;
                // temporarily override the login failure message for this request
                Event::on(
                    UsersController::class,
                    UsersController::EVENT_LOGIN_FAILURE,
                    function (LoginFailureEvent $event) {
                        $event-&gt;message = &quot;This account is already logged in at another location.&quot;;
                    }
                );
                return;
            }
        }
    }
);
</code></pre>
<p>The <code>UserEvent</code> has a variable <code>$duration</code> which I believe is set by the general config variable <code>userSessionDuration</code>, which is in seconds.</p>
<p>For non-CP requests, this method should only allow a single login for a user account, at one time.</p>
<p>I added another event handler on <code>UsersController::EVENT_LOGIN_FAILURE</code> to set a custom error message when a login fails due to another active session for that user.</p>
<p>Seems to be working so far... hope this helps someone else. Feel free to poke holes!</p>

CC BY-SA 4.0
2021-01-07T19:56:36.887Z
2021-01-08T00:33:12.070Z
2021-01-08T00:33:12.070Z
Gary Reckard
<p>This can be done with a plugin.</p>

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

<p>You can insert the first row by listening to the <a href="http://buildwithcraft.com/docs/plugins/events-reference#userSession-onLogin" rel="noreferrer">onLogin</a> event and remove their row by listening to the <a href="http://buildwithcraft.com/docs/plugins/events-reference#userSession-onLogout" rel="noreferrer">onLogout</a>.</p>

<p>All subsequent requests, you'd compare the last known activity timestamp to the <a href="http://buildwithcraft.com/docs/config-settings#userSessionDuration" rel="noreferrer">userSessionDuration</a> config setting to still see if they have an active session.</p>

<p>You'd also be listening to the <a href="http://buildwithcraft.com/docs/plugins/events-reference#userSession-onBeforeLogin" rel="noreferrer">onBeforeLogin</a> 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 <code>$performAction = false</code> as a property of the event, and that will prevent the login action from occurring.</p>

CC BY-SA 3.0
2014-12-05T23:52:53.603Z
Thanks for all the details, Brad! I haven't worked on plugins yet but I guess I had to start at some point.
cballenar
CC BY-SA 3.0
2016-03-01T15:01:00.683Z
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
CC BY-SA 3.0
2014-12-05T23:00:20.280Z
2014-12-05T23:00:20.280Z
Brad Bell
<p>Is there a way to limit the number of sessions per user? That is, I can be logged into one machine/browsers only once.</p>

<p>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.</p>

<p>Has anyone dealt with this situation?</p>

CC BY-SA 3.0
2014-12-05T18:17:30.027Z
Curious... what's the use case for this?
Brad Bell
CC BY-SA 3.0
2014-12-05T21:40:52.917Z
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
CC BY-SA 4.0
2020-08-18T12:17:14.250Z
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
<users><session><configuration>
Limit number of sessions per user
# Limit number of sessions per user

(Asked by: username_0 on 2014-12-05)

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?

**Tags:** users, session, configuration
> **Comments:**
> **username_1**: Curious... what's the use case for this?  
> **username_0**: 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.  
> **username_2**: 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 ?  

---

## 2 answers

### Answer by username_1 on 2014-12-05. Score: 5 | Accepted answer

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](http://buildwithcraft.com/docs/plugins/events-reference#userSession-onLogin) event and remove their row by listening to the [onLogout](http://buildwithcraft.com/docs/plugins/events-reference#userSession-onLogout).

All subsequent requests, you'd compare the last known activity timestamp to the [userSessionDuration](http://buildwithcraft.com/docs/config-settings#userSessionDuration) config setting to still see if they have an active session.

You'd also be listening to the [onBeforeLogin](http://buildwithcraft.com/docs/plugins/events-reference#userSession-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.

> **Comments:**
> **username_0**: Thanks for all the details, Brad! I haven't worked on plugins yet but I guess I had to start at some point.  
> **username_4**: 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.  

---

### Answer by username_3 on 2021-01-07. Score: 5

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!


---

