[Fixed]-How to limit number of concurrent users logging in to same account in Django

12👍

To limit the concurrent users, keep an eye on the existing sessions.

In your current approach, when a user logs in, a new session is created. That new session co-exists with the older sessions, so you have N concurrent sessions at the same time.

You want to allow a single session. The easiest approach would be to invalidate older session when a new login happens:

Other (more complete, but more complex) approaches would be using Two-factor authentication, blocking per IP, throttling the login event, requiring email confirmation, etc…

3👍

Store User-Session mapping in another model.

from django.conf import settings
from django.contrib.sessions.models import Session
from django.db import models

class UserSessions(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_sessions')
    session = models.OneToOneField(Session, related_name='user_sessions',
                                   on_delete=models.CASCADE)

    def __str__(self):
        return '%s - %s' % (self.user, self.session.session_key)

If you have your own login view, you can update this model yourself:

from django.contrib.auth.views import login as auth_login

def login(request):
    auth_login(request)
    if request.user.is_authenticated():
        session = Session.objects.get(session_key=request.session.session_key)
        user_session = UserSession.objects.create(user=request.user, session=session)
    no_of_logins = request.user.user_sessions.count()
    if no_of_logins > 1:  # whatever your limit is
        request.SESSION['EXTRA_LOGIN'] = True
        # Do your stuff here

Other option is to use Signal. Django provides signals: user_logged_in, user_login_failed and user_logged_out, if you use the Django login view, that is.

# signals.py
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

@receiver(user_logged_in)
def concurrent_logins(sender, **kwargs):
    user = kwargs.get('user')
    request = kwargs.get('request')
    if user is not None and request is not None:
        session = Session.objects.get(session_key=request.session.session_key)
        UserSessions.objects.create(user=user, session=session)
    if user is not None:
        request.session['LOGIN_COUNT'] = user.user_sessions.count()

# your login view
def login(request):
     auth_login(request)
     if request.user.is_authenticated() and request.session['LOGIN_COUNT'] > 1:
         # 'LOGIN_COUNT' populated by signal
         request.session['EXTRA_LOGIN'] = True
         # Do your stuff

If EXTRA_LOGIN is True, you can list the previous sessions, and ask the user to choose which sessions to logout. (Don’t stop him from logging in, else he might be locked out – if he doesn’t have access to his previous sessions now)

3👍

1 In your users/profiles app add a management command file

To add managment command, follow this guide:
https://docs.djangoproject.com/en/1.10/howto/custom-management-commands/

2 The management command code:
kills all sessions from users that have more then 10 sessions, you may change that to 1K if needed, or send this value as a param to the management command

from django.core.management.base import BaseCommand, CommandError
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User

class Command(BaseCommand):
    def handle(self, *args, **options):
        session_user_dict = {}


        # users with more than 10 sessions - del all

        for ses in Session.objects.all():
            data = ses.get_decoded()
            user_owner = User.objects.filter(pk = data.get('_auth_user_id', None))

            if int(data.get('_auth_user_id', None)) in session_user_dict:
                session_user_dict[int(data.get('_auth_user_id', None))] += 1
            else:
                session_user_dict[int(data.get('_auth_user_id', None))] = 1

        for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()

3 Optional password change-
after killing the bad-users sessions -change the bad users password to a diff one.
To do that change the last loop in the above code

            for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()
                        theuser =  User.objects.filter(pk=k)
                        #maybe use uuid to pick a password ...
                        theuser.set_password('new_unknown_password')

4 Add a django management command to crontab every minute / hour or whenever use this guide:
https://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

if you are using a virtual env , remember that a management command that runs from cron needs to enter to the virtual env first, you may do it with a .sh script, ask for help if needed

Leave a comment