DEV Community

ashrafZolkopli
ashrafZolkopli

Posted on

Preventing Multiple Login for a Single Django User

Sometime website or a simple app implement a one login at a time function, like snapchat, or whatapp ( I'm not sure how long the whatapp example will hold true since there are hints that whatapp might be able to use on 4 concurrent device at a time). Anyway it might be a business logic requirement or even from a security stand point.

I used to thing maybe it would be hard to implement such a behaviour until I found a package called django-preventconcurrentlogins. Yes the name is that long but you know what, it does its job so well that sometime while testing, Me and my colleague who are using the same ID kinda wonder why we kept having to login...

One thing to note about this application, this applies to all authenticated user to your site whether its just a normal user or staff. So in this post I will go through how I would implement the logic for all Authenticated User using the django-preventconcurrentlogins and also using the technique in the package that apply only for user with a staff status.

Installing django-preventconcurrentlogins

the library is simple enough that its only need this command :

pipenv install django-preventconcurrentlogins
pipenv lock -r > requirements.txt
Enter fullscreen mode Exit fullscreen mode

Configure Setting.py for django-preventconcurrentlogins

open your settings.py file and add the following into the parts:

INSTALLED_APPS = {        
    #...
    'preventconcurrentlogins',
    #...
    }
Enter fullscreen mode Exit fullscreen mode
MIDDLEWARE_CLASSES = {        
    #...
    'preentconcurrentlogins.middleware.PreventConcurrentLoginsMiddleware',
    #...
   }
Enter fullscreen mode Exit fullscreen mode

this time you need to add a migrate since django-preventconcurrentlogins have some modal that needed to be migrated to your db

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

And your done.

Rolling our own library

The way I see it, I want my staff user to only have Session at a time due to the nature of their work and let the Customer ( normal user ) to have as many user session as they want without killing their previous session. I know it might not look like it sound good but honestly... I would rather have my staff which have a higher chance of messing things up to be bothered with this security implementation when letting things get mess up.

I'm not sure if I should work getting this become a package since I am gonna be using this a lot... maybe I would then... but for now lets just work on having this located in our User app.

Models.py

Lets start will our models.py in the User app.

from django.db import models
from django.utils.translation import gettext_lazy as _

class StaffLogin(models.Model):
    staff = models.ForeignKey(
        User,
        verbose_name=_("Staff"),
        on_delete=models.CASCADE,
        related_name='user_stafflogin',
        limit_choices_to={'is_staff': True},
    )

    session_key = models.CharField(
        max_length=40,
        verbose_name=_("Session Key"),
    )
Enter fullscreen mode Exit fullscreen mode

now lets create a middleware.py file in User app

from .models import StaffLogin
from importlib import import_module
from django.conf import settings

SessionStore = import_module(settings.SESSION_ENGINE).SessionStore


class PreventConcurrentStaffLogin:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request, *args, **kwargs):
        if not request.user.is_staff:
            return self.get_response(request)

        staff_session = request.session.session_key

        if not hasattr(request.user, 'staff_stafflogin'):
            StaffLogin.objects.create(
                staff=request.user,
                session_key=staff_session,
            )
            return self.get_response(request)

        staff_db_session = request.user.staff_stafflogin.session_key
        if staff_session != staff_db_session:
            SessionStore(staff_db_session).delete()
            request.user.staff_stafflogin.session_key = staff_session
            request.user.staff_stafflogin.save()

        return self.get_response(request)
Enter fullscreen mode Exit fullscreen mode

Now we update our middleware in our settings.py file

MIDDLEWARE = [
    #... must be after 
    # django.contrib.auth.middleware.AuthenticationMiddleware

    #Custom Middleware Prevent Concurrent Staff Login
    'User.middleware.PreventConcurrentStaffLogin'
    #...
]
Enter fullscreen mode Exit fullscreen mode

now we should make a make mirgations and migrate since we now have a new model that we need to register in our db.

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Our web app should by now be able to make sure all our staff can only have one login session active at a time.

End

In simple terms, now we are able to keep our user from having a concurrent login while using our app. Hopefully this will make our user much more safe, in a sense if someone else had login into our user, our user just have to login again so that the old session ( someone else that log into our user account ) will be deleted.

Top comments (2)

Collapse
 
chirayurathi profile image
chirayu rathi

does it work with jwt

Collapse
 
ashraf_zolkopli profile image
ashrafZolkopli

This method assume that the admin is using Django application that is being served directly by Django. To make it work with JWT, We need to use either Django Rest Framework ( DRF ) with a specific permission design to allow only one login per staff user. This is doable but beyon the topic of this post.