Associating Django users with their sessions
When writing a Django application that authenticates users using sessions, you’ll often want to have some way of finding all of the active sessions for a particular user. An example use case might be finding all existing user sessions and deleting them after a password change, ensuring there isn’t anyone still able to access your site via an old login session.
Ultimately, you’d want to be able to do something like this:
def delete_user_sessions(user): user.sessions.delete()
This is a bit difficult with out-of-the-box Django, as session data is stored as a serialised hash, with no direct association with a particular user.
One solution is to iterate through every single stored session, deserialise the data it contains, and search the user ID from the extracted data against the ID of the user we’re concerned with. This method is obviously quite inefficient and on a system with many concurrent sessions, things can get pretty slow - we’re doing a full table scan every time.
A couple of alternate solutions were proposed in this Stack Overflow thread:
- Use the user’s
last_login_dateto restrict the scope of the full table scan when searching for matching sessions; or
- Add a
user_idparameter to the
django_sessiontable (through forking the
The first solution doesn’t really solve the performance issue of iterating and deserialising. It also feels like we could run in to trouble if we make a mistake calculating the date range to search across, or if we decided to change the expiration length for our sessions.
The second solution is better, but it’s a bit of a code smell as we’re duplicating core Django code.
Not only is the duplication itself poor coding practice, we’re going to be overriding any future updates to
django.contrib.session that are shipping with newer versions of Django.
My solution for this works on the assumption that you’re only concerned with logged-in user sessions (ie, you’re not worried about anonymous user sessions), which should be the most common case.
It’s also assumes that you’re using either
django.contrib.sessions.backends.cached_db as your
django.contrib.sessions.backends.db is the default with Django, so if you haven’t changed anything on this front, you’re all set.
First, we create a database model to link a
User to a
from django.conf import settings from django.db import models from django.contrib.sessions.models import Session class UserSession(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL) session = models.ForeignKey(Session)
Then, we simply save an instance of this mapping model every time a user logs in, through the use of Django’s
from django.contrib.auth.signals import user_logged_in def user_logged_in_handler(sender, request, user, **kwargs): UserSession.objects.get_or_create( user = user, session_id = request.session.session_key ) user_logged_in.connect(user_logged_in_handler)
That’s really all we need to do to keep the user associated with their sessions.
Now, we can implement
delete_user_sessions() like this:
from .models import UserSession def delete_user_sessions(user): user_sessions = UserSession.objects.filter(user = user) for user_session in user_sessions: user_session.session.delete()
Because of the way Django’s
ForeignKey relations work on deletion, calling
user_session.session.delete() will also remove the associated
This will also be the case if you’re cleaning up expired sessions through a cron job or similar.