113

Our Django application has the following session management requirements.

  1. Sessions expire when the user closes the browser.
  2. Sessions expire after a period of inactivity.
  3. Detect when a session expires due to inactivity and display appropriate message to the user.
  4. Warn users of a impending session expiry a few minutes before the end of the inactivity period. Along with the warning, provide users an option to extend their session.
  5. If user is working on a long business activity within the app that doesn't involve requests being sent to the server, the session must not timeout.

After reading the documentation, Django code and some blog posts related to this, I have come up with the following implementation approach.

Requirement 1
This requirement is easily implemented by setting SESSION_EXPIRE_AT_BROWSER_CLOSE to True.

Requirement 2
I have seen a few recommendations to use SESSION_COOKIE_AGE to set the session expiry period. But this method has the following problems.

  • The session always expires at the end of the SESSION_COOKIE_AGE even if the user is actively using the application. (This can be prevented by setting the session expiry to SESSION_COOKIE_AGE on every request using a custom middleware or by saving the session on every request by setting SESSION_SAVE_EVERY_REQUEST to true. But the next problem is unavoidable due to the use of SESSION_COOKIE_AGE.)

  • Due to the way cookies work, SESSION_EXPIRE_AT_BROWSER_CLOSE and SESSION_COOKIE_AGE are mutually exclusive i.e. the cookie either expires on browser close or at the specified expiry time. If SESSION_COOKIE_AGE is used and the user closes the browser before the cookie expires, the cookie is retained and reopening the browser will allow the user (or anyone else) into the system without being re-authenticated.

  • Django relies only on the cookie being present to determine if the session is active. It doesn't check the session expiry date stored with the session.

The following method could be used to implemented this requirement and to workaround the problems mentioned above.

  • Do not set SESSION_COOKIE_AGE.
  • Set the expiry date of the session to be 'current time + inactivity period' on every request.
  • Override process_request in SessionMiddleware and check for session expiry. Discard the session if it has expired.

Requirement 3
When we detect that the session has expired (in the custom SessionMiddleware above), set an attribute on the request to indicate session expiry. This attribute can be used to display an appropriate message to the user.

Requirement 4
Use JavaScript to detect user inactivity, provide the warning and also an option to extend the session. If the user wishes to extend, send a keep alive pulse to the server to extend the session.

Requirement 5
Use JavaScript to detect user activity (during the long business operation) and send keep alive pulses to the server to prevent session from expiring.


The above implementation approach seem very elaborate and I was wondering if there might a simpler method (especially for Requirement 2).

Any insights will be highly appreciated.

Akbar ibrahim
  • 5,110
  • 3
  • 26
  • 23
  • 3
    +1 for providing a detailed solution – Don Jun 07 '12 at 15:55
  • 2
    "Due to the way cookies work, SESSION_EXPIRE_AT_BROWSER_CLOSE and SESSION_COOKIE_AGE are mutually exclusive i.e. the cookie either expires on browser close or at the specified expiry time. If SESSION_COOKIE_AGE is used and the user closes the browser before the cookie expires, the cookie is retained and reopening the browser will allow the user (or anyone else) into the system without being re-authenticated." Correct me if I'm wrong, but this does not seem to be true anymore in newer Django versions? (1.5+ at least) – Botond Béres Nov 04 '14 at 12:53
  • There is a middleware that may do what you need. [on github](https://github.com/subhranath/django-session-idle-timeout) and [on pypi](http://pypi.python.org/pypi/django-session-idle-timeout/1.3.1) – gbutler Feb 12 '13 at 22:50
  • 1
    "Django relies only on the cookie being present to determine if the session is active. It doesn't check the session expiry date stored with the session." This is [not true](https://github.com/django/django/blob/1.11.8/django/contrib/sessions/backends/db.py#L35) anymore. – knaperek Dec 08 '17 at 09:40
  • In 2020, the second answer should be the accepted one. The currently accepted one adds complexity for something already implemented on Django. – WhyNotHugo Nov 26 '20 at 20:36

7 Answers7

53

I am just pretty new to use Django.

I wanted to make session expire if logged user close browser or are in idle(inactivity timeout) for some amount of time. When I googled it to figure out, this SOF question came up first. Thanks to nice answer, I looked up resources to understand how middlewares works during request/response cycle in Django. It was very helpful.

I was about to apply custom middleware into my code following top answer in here. But I was still little bit suspicious because best answer in here was edited in 2011. I took more time to search little bit from recent search result and came up with simple way.

SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_AGE = 10 # set just 10 seconds to test
SESSION_SAVE_EVERY_REQUEST = True

I didn't check other browsers but chrome.

  1. A session expired when I closed a browser even if SESSION_COOKIE_AGE set.
  2. Only when I was idle for more than 10 seconds, A session expired. Thanks to SESSION_SAVE_EVERY_REQUEST, whenever you occur new request, It saves the session and updates timeout to expire

To change this default behavior, set the SESSION_SAVE_EVERY_REQUEST setting to True. When set to True, Django will save the session to the database on every single request.

Note that the session cookie is only sent when a session has been created or modified. If SESSION_SAVE_EVERY_REQUEST is True, the session cookie will be sent on every request.

Similarly, the expires part of a session cookie is updated each time the session cookie is sent.

django manual 1.10

I just leave answer so that some people who is a kind of new in Django like me don't spend much time to find out solution as a way I did.

smac89
  • 39,374
  • 15
  • 132
  • 179
Jayground
  • 1,797
  • 1
  • 17
  • 32
48

Here's an idea... Expire the session on browser close with the SESSION_EXPIRE_AT_BROWSER_CLOSE setting. Then set a timestamp in the session on every request like so.

request.session['last_activity'] = datetime.now()

and add a middleware to detect if the session is expired. something like this should handle the whole process...

from datetime import datetime
from django.http import HttpResponseRedirect

class SessionExpiredMiddleware:
    def process_request(request):
        last_activity = request.session['last_activity']
        now = datetime.now()

        if (now - last_activity).minutes > 10:
            # Do logout / expire session
            # and then...
            return HttpResponseRedirect("LOGIN_PAGE_URL")

        if not request.is_ajax():
            # don't set this for ajax requests or else your
            # expired session checks will keep the session from
            # expiring :)
            request.session['last_activity'] = now

Then you just have to make some urls and views to return relevant data to the ajax calls regarding the session expiry.

when the user opts to "renew" the session, so to speak, all you have to do is set requeset.session['last_activity'] to the current time again

Obviously this code is only a start... but it should get you on the right path

Jiaaro
  • 74,485
  • 42
  • 169
  • 190
  • I'm just being skeptical here, but I don't think the `if not request.is_ajax()` is completely safe. Can't someone who gets a hold of the session pre-expiry spoof/send an ajax call and keep the session going? – notbad.jpeg May 11 '13 at 03:53
  • 2
    @notbad.jpeg: in general "activity" is easily spoofable. Someone who gets hold of the session and keeps sending requests is just active. – RemcoGerlich Aug 15 '14 at 07:38
  • This is a great answer. Middleware is a very underutilized tool in Django development. – Jamie Counsell Apr 26 '16 at 13:57
  • @jiaaro You say the code is just a start but it looks fairly complete to me. If it's missing anything could you please be more explicit in your answer about what it is missing. – ferreiradev Mar 17 '23 at 16:27
28

django-session-security does just that...

... with an additional requirement: if the server doesn't respond or an attacker disconnected the internet connection: it should expire anyway.

Disclamer: I maintain this app. But I've been watching this thread for a very, very long time :)

jpic
  • 32,891
  • 5
  • 112
  • 113
16

One easy way to satisfy your second requirement would be to set SESSION_COOKIE_AGE value in settings.py to a suitable amount of seconds. For instance:

SESSION_COOKIE_AGE = 600      #10 minutes.

However, by only doing this the session will expire after 10 minutes whether or not the user exhibits some activity. To deal with this issue, expiration time can be automatically renewed (for another extra 10 minutes) every time the user performs any kind of request with the following sentence:

request.session.set_expiry(request.session.get_expiry_age())
Fernando Martin
  • 609
  • 6
  • 19
  • 2
    SESSION_COOKIE_AGE = 600 This will extend sessions age with every new page request or page refresh – Aseem Dec 10 '18 at 08:01
  • 2
    I confirm that just setting `SESSION_COOKIE_AGE` is enough and that any request (sending the session cookie) will automatically refresh the session cookie expiration. – bruno desthuilliers Feb 11 '19 at 12:46
5

also you can use stackoverflow build in functions

SESSION_SAVE_EVERY_REQUEST = True
Community
  • 1
  • 1
mexekanez
  • 266
  • 3
  • 7
  • 1
    Totally unrelated - this is for forcing a _session_ save (not "session cookie refresh") on each request without checking whether the session has been modified. – bruno desthuilliers Feb 11 '19 at 12:49
  • @brunodesthuilliers Actually it's not unrelated. Saving the session also updates the expiry time of the session. – atleta May 31 '22 at 01:28
4

In the first request, you can set the session expiry as

self.request.session['access_key'] = access_key
self.request.session['access_token'] = access_token
self.request.session.set_expiry(set_age) #in seconds 

And when using the access_key and token,

try:
    key = self.request.session['access_key']
except KeyError:
    age = self.request.session.get_expiry_age()
    if age > set_age:
        #redirect to login page
hlkstuv_23900
  • 864
  • 20
  • 34
0

I'm using Django 3.2 and i recommend using the django-auto-logout package.

It allows active time and idle time session control.

In the template you can use variables together with Javascript.

  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/30792512) – Simas Joneliunas Jan 14 '22 at 15:47