Cookie-based Session Storage for Django

28 May 2009
20:34

By cmlenz as Python

Here's a simple cookie-based session backend for Django. Session data is JSON encoded and GZIP compressed, and then signed with a MD5 hash based on a server-side secret key. To use this, the default Django session middleware needs to be replaced with the one in this module. The SESSION_ENGINE is henceforth ignored.

1 from base64 import b64decode, b64encode
2 import md5
3 from time import time
4 import zlib
5
6 from django.conf import settings
7 from django.contrib.sessions.backends.base import SessionBase
8 from django.core.exceptions import SuspiciousOperation
9 from django.utils import simplejson
10 from django.utils.cache import patch_vary_headers
11 from django.utils.encoding import force_unicode
12 from django.utils.http import cookie_date
13
14 MAX_COOKIE_SIZE = 4096
15
16
17 class SessionMiddleware(object):
18
19 def process_request(self, request):
20 cookie = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
21 request.session = SessionStore(cookie)
22
23 def process_response(self, request, response):
24 session = request.session
25 if session.deleted:
26 response.delete_cookie(settings.SESSION_COOKIE_NAME)
27 else:
28 if session.accessed:
29 patch_vary_headers(response, ('Cookie',))
30 if session.modified or settings.SESSION_SAVE_EVERY_REQUEST:
31 if session.get_expire_at_browser_close():
32 max_age = None
33 expires = None
34 else:
35 max_age = session.get_expiry_age()
36 expires = cookie_date(time() + max_age)
37 cookie = session.encode(session._session)
38 if len(cookie) <= MAX_COOKIE_SIZE:
39 response.set_cookie(settings.SESSION_COOKIE_NAME, cookie,
40 max_age = max_age, expires=expires,
41 domain = settings.SESSION_COOKIE_DOMAIN,
42 path = settings.SESSION_COOKIE_PATH,
43 secure = settings.SESSION_COOKIE_SECURE or None
44 )
45 else:
46 # The data doesn't fit into a cookie, not sure what's the
47 # best thing to do in this case. Right now, we just leave
48 # the old cookie intact if there was one. If Django had
49 # some kind of standard logging interface, we could also
50 # log a warning here.
51 pass
52 return response
53
54
55 class SessionStore(SessionBase):
56
57 def __init__(self, cookie):
58 SessionBase.__init__(self, 'cookie')
59 self.cookie = cookie
60 self.deleted = False
61
62 def exists(self, session_key):
63 return self.cookie and not self.deleted
64
65 def create(self):
66 pass
67
68 def save(self, must_create=False):
69 pass
70
71 def delete(self, session_key=None):
72 self.deleted = True
73
74 def load(self):
75 if self.cookie:
76 return self.decode(self.cookie)
77 return {}
78
79 def cycle_key(self):
80 pass
81
82 def encode(self, session_dict):
83 json = simplejson.dumps(session_dict)
84 json_md5 = md5.new(json + settings.SECRET_KEY).hexdigest()
85 try:
86 return b64encode(zlib.compress(json + json_md5))
87 except Exception, e:
88 return ''
89
90 def decode(self, session_data):
91 try:
92 data = zlib.decompress(b64decode(session_data))
93 except Exception, e:
94 return {}
95 json, json_md5 = data[:-32], data[-32:]
96 if md5.new(json + settings.SECRET_KEY).hexdigest() != json_md5:
97 raise SuspiciousOperation('User tampered with session cookie')
98 return simplejson.loads(json)

Download Raw Source

Comments

  1. cmlenz says:

    28 May 2009
    21:01

    Ideally, this would use httpOnly, but I don't think the Django cookies API supports that yet.
  2. anonymous says:

    21 July 2009
    20:51

    Hello, I am new to django. Can you contribute this code to the django community, perhaps in the way that user can use this code if he / she has SESSION_ENGINE = django.contrib.sessions.backends.cookie ?
  3. clay@daemons.net says:

    5 August 2009
    05:40

    Very nice, thanks for this, I was just about to implement my own version but have found yours eminently readable. I'm curious, why are signed session cookies more prevalent than encrypted session cookies? It seems to me that encryption would provide the same tamper resistance as signing, while also obscuring the cookie contents from prying users. Is simple symmetric key encryption based on a static secret not strong enough?
  4. justquick says:

    21 October 2009
    17:14

    I have cleaned up the code and fixed a bug: users could login, but since the domain was not passed when deleting the cookie, users could not logout sucessfully. Here is a full list of the changes: 1) Delete cookie bug fixed. 2) Uses SHA1 instead of MD5. 3) Uses django.utils.hashcompat instead of native module. 4) MAX_COOKIE_SIZE can be overridden in settings.py. 5) Uses encode/decode string methods instead of zlib module. The source code of the updated file is published as a gist here http://gist.github.com/215274
  5. mzl says:

    19 February 2010
    12:52

    This looks like a nice way to add some simple session info in django. Is there any open-source license attached to the code so that one could use it in a project?