Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing only single active session per user in Django app

I want to restrict logged-in users to only have one active session, i.e. if the user logs in with a new sessionid, the old session should be terminated. I found a lot of help on SO already: here and here

I implemented the middleware solution, with a bit of extra checking...

class OnlyOneUserMiddleware(object):
"""
Middleware to ensure that a logged-in user only has one session active.
Will kick out any previous session. 
"""
def process_request(self, request):
    if request.user.is_authenticated():
        try:
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                Session.objects.get(session_key=cur_session_key).delete()
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()
        except ObjectDoesNotExist:
            pass

So far, so good... on the Django dev server (manage.py runserver) everything works properly, it kicks the old session...

...but when using Apache ( with mod_wsgi), it doesn't work!

I've tried to find any information about this, but no luck so far...

The closest I have found is this, but it is kind of the 'opposite' problem...

Any help would be much appreciated.

Edit: I added a debug print before deleting the Session... here's a snippet from Apache's error.log:

[Fri Jan 20 09:56:50 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:56:50 2012] [error] new key = ce4cfb672e6025edb8ffcd0cf2b4b8d1
[Fri Jan 20 09:57:14 2012] [error] old key = f42885ccb7f33b6afcb2c18fca14f44a
[Fri Jan 20 09:57:14 2012] [error] new key = 0815c56241ac21cf4b14b326f0aa7e24

the first two lies are from when I entered with the first session (Firefox)

the last two are from when I entered with the second session (Chromium)

... it turns out that the old Session record does not get deleted... ???

I'm running vs. the exact same PostgreSQL instance as I did with the devserver...

Edit2: It turned out that my code was buggy... it failed when the new Session_key wasn't found in Session...

here's the fixed code... the try..except is now in the correct place

class OnlyOneUserMiddleware(object):
    """
    Middleware to ensure that a logged-in user only has one session active.
    Will kick out any previous session. 
    """
    def process_request(self, request):
        if request.user.is_authenticated():
            cur_session_key = request.user.get_profile().session_key
            if cur_session_key and cur_session_key != request.session.session_key:
                # Default handling... kick the old session...
                try:
                    s = Session.objects.get(session_key=cur_session_key)
                    s.delete()
                except ObjectDoesNotExist:
                    pass
            if not cur_session_key or cur_session_key != request.session.session_key:
                p = request.user.get_profile()
                p.session_key = request.session.session_key
                p.save()
like image 530
Mark Johansson Avatar asked Jan 19 '12 14:01

Mark Johansson


People also ask

How are sessions maintained in Django?

Django uses a cookie containing a special session id to identify each browser and its associated session with the site. The actual session data is stored in the site database by default (this is more secure than storing the data in a cookie, where they are more vulnerable to malicious users).

How do I stop a session in Django?

To delete a session or any particular key of that session, we can use del. The output will look like this and don't worry if your cookie didn't delete because we use this method only to delete your data in the Django database and not the session ID and cookie itself.


2 Answers

There is indeed a lot of similar questions all over the place, but here is my solution.

When a user logins go over all active sessions and remove the ones with the same user.id. For smaller websites, this should do just fine.

# __init__.py # Logs user out from all other sessions on login, django 1.8  from django.contrib.sessions.models import Session from django.contrib.auth.signals import user_logged_in from django.db.models import Q from django.utils import timezone  def limit_sessions(sender, user, request, **kwargs):     # this will be slow for sites with LOTS of active users      for session in Session.objects.filter(         ~Q(session_key = request.session.session_key),         expire_date__gte = timezone.now()     ):         data = session.get_decoded()         if data.get('_auth_user_id', None) == str(user.id):             # found duplicate session, expire it             session.expire_date = timezone.now()             session.save()      return  user_logged_in.connect(limit_sessions) 
like image 121
MarZab Avatar answered Sep 27 '22 16:09

MarZab


You can always use this approach though not recommended, it works.

my_old_sessions = Session.objects.all() for row in my_old_sessions:    if row.get_decoded().get("_username") == request.user.username:       row.delete() 

You would implement the code above in your login() function right before authenticating the user.

This of course only works if you have a login() function method that stores the USERS username in his session like follows:

request.session["_username"] = request.user.username 

If you use this approach just remember to empty your database of all of your sessions before running your server after you've made these changes because it will raise KeyLookUp errors.

like image 44
SuperA Avatar answered Sep 27 '22 16:09

SuperA