Wednesday, February 6, 2013

Restricting django to one user concurrent session only

Here's the tiny code that helps you avoid multiple users logged using the same account.
# -*- coding: utf-8 -*

from django.conf import settings
from django.core.cache import cache, get_cache
from django.utils.importlib import import_module


class UserRestrictMiddleware(object):
    def process_request(self, request):
        """
        Checks if different session exists for user and deletes it.
        """
        if request.user.is_authenticated():
            cache = get_cache('default')
            cache_timeout = 86400
            cache_key = "user_pk_%s_restrict" % request.user.pk
            cache_value = cache.get(cache_key)

            if cache_value is not None:
                if request.session.session_key != cache_value:
                    engine = import_module(settings.SESSION_ENGINE)
                    session = engine.SessionStore(session_key=cache_value)
                    session.delete()
                    cache.set(cache_key, request.session.session_key, 
                              cache_timeout)
            else:
                cache.set(cache_key, request.session.session_key, cache_timeout)

# vim: ai ts=4 sts=4 et sw=4
Hope you like it.
Remember, to put UserRestrictMiddleware somewhere after Session Middleware in MIDDLEWARE_CLASSES (settings.py)

4 comments:

tentacles said...

If user A accesses the site and then user B comes along with the same credentials (but a different session), then presumably session.delete() will mean that user A is logged out?

Presumably you could replace session.delete() with request.session.delete() to achieve the effect of B being logged out while A remains logged in?

(Apologies, I only dabble in Django... I was trying to work out whether there is a race condition in the code.)

Robert said...

Thanks for your comment.

If I did this the way you wrote (User B can't log in until user A logs out) than User A would lock himself out and would not be able to log in from another place (initiating new session).That's not what I meant to achieve.

The design was to disable users multi login in one of paid services I manage. If user A provides his user password to another person than he will be locked out each time this person logs in (and vice-versa).

This is how I understood your post.
Let me know if you see this the other way.

My script is not perfect. It presumes that between the time of logging in both User A and User B cache was not cleared.

It could be done with an additional last_session = models.CharField() key attached to UserProfile (or new User model Django >= 1.5) but it would hit the database each time.

I am using redis instance for this "caching" which is not cleared often.

Thanks

Jota said...

This Middleware also prevents multiple login in same browser? I mean multiple tabs of the same web service.
If not, how i can do that?

Robert said...

@Jota

Why would you like to prevent using multiple tabs from the same web browser ?

Multiple tabs share the same session, so it will (blocking) work only if you open a new "private/incognito mode" tab as it doesn't share the same session.

Also my solution prevents from using the same account on 2 different browsers on the same computer.