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
Configure Setting.py for django-preventconcurrentlogins
open your settings.py file and add the following into the parts:
INSTALLED_APPS = {
#...
'preventconcurrentlogins',
#...
}
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.
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
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
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.
Top comments (2)
does it work with jwt
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.