My site is a digital marketplace website written in Django.
Digital content(text, images, videos) on the site is 'locked' by default. Only users who bought those content can view it.
There's a story that certain user(who bought the content) give away username/password for free to many people(1,000+ people in Facebook groups, for example). Those 1,000 users can then login using that single username/password and view the 'locked' digital content without paying a cent.
Is it possible to limit number of concurrent login to the same account?
I've found this package:
https://github.com/pcraston/django-preventconcurrentlogins
but what it does is logging previous user out when someone logged in using the same username/password. That would not help because each user only need to type in username/password each time to access 'locked' content.
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...
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)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With