Preventing Multiple Login for a Single Django User 
Tags: django, github, template, tutorial
Published: 2021-06-12 02:37:22

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](https://github.com/pcraston/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
```

# Configure Setting.py for django-preventconcurrentlogins

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

```python
INSTALLED_APPS = {        
    #...
    'preventconcurrentlogins',
    #...
    }
```
```python 
MIDDLEWARE_CLASSES = {        
    #...
    'preentconcurrentlogins.middleware.PreventConcurrentLoginsMiddleware',
    #...
   }
```

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
```

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.

```python 
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"),
    )
```

now lets create a middleware.py file in User app

```python
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)
```

Now we update our middleware in our settings.py file

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

    #Custom Middleware Prevent Concurrent Staff Login
    'User.middleware.PreventConcurrentStaffLogin'
    #...
]
```

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
```

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.