2

::Edit::

@cache_control(no_cache=True, must_revalidate=True, no_store=True) FTW!!!!!

Cache-Control: no-cache, no-store, must-revalidate did the trick. It took going to a few IRC chans and looking around but finally I got it to work.

::EDIT::

I have a view where I'm setting @login_required on it and its secure for the most part, but if you have looked at the view then logout and just hit the back button in your browser you can view the content again with out being asked to login. Though if you refresh the page the server with will redirect you.

My suspension is its a cache issue where maybe I need to tell chrome not to store it in the history.

if you view a invoice for example then logout you can view the invoice again by selecting that page in your back history.

I have tried this issue o firefox with no problem. firefox asks for you to log back end so it must be a browser issue.

Polygon Pusher
  • 2,865
  • 2
  • 27
  • 32
  • Whether or not the page shows up in the browser's history has nothing to do with `login_required`. That's going to show up regardless. However the page should not load as if the user was logged in when they are not. If that's happening, that's an entirely different issue. – Chris Pratt Mar 08 '12 at 21:37
  • Sorry for not being more knowledgeable about issue. I described it as best as I understood the issue. Regardless I feel that upon hitting the back button the content should not be view able if the user is no longer logged in, of which is not a problem with firefox. – Polygon Pusher Mar 08 '12 at 21:50
  • There is very similar question here: http://stackoverflow.com/questions/6923027/disble-back-buttonon-browser-after-logout-in-django – Marius Grigaitis Mar 08 '12 at 22:02
  • Did you edit this question to put the answer into the question? Painful. Why not just edit it to be clear, and let the accepted answer stand as the answer? – eric Sep 01 '17 at 04:07

4 Answers4

6

You're right, this is cache problem.

You can use cache_control decorator to force no cache on views[1]:

from django.views.decorators.cache import cache_control

@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def func()
  #some code
  return

You should also write your own decorator that replaces @login_required so that you don't need to use both on every page.

[1] Disable browser 'Back' button after logout?

Community
  • 1
  • 1
Marius Grigaitis
  • 2,520
  • 3
  • 23
  • 30
  • So I have checked that there is indeed "Cache-Control: no-cache, must-revalidate" in the header but its still showing the page regardless. thats with the use of @cache_control(no_cache=True, must_revalidate=True) and I have tried @never_cache – Polygon Pusher Mar 08 '12 at 22:54
  • very close, thanks for the help though. I found that @cache_control(no_cache=True, must_revalidate=True, no_store=True) worked. – Polygon Pusher Mar 08 '12 at 23:35
  • Edited the answer as Digital Cake suggested – Marius Grigaitis Mar 08 '12 at 23:40
  • Very helpful, but I wonder about `You should also write your own decorator that replaces @login_required so that you don't need to use both on every page.` While it may give a little bit of DRY, it arguably would make for less readable code. Seems very easy to have two little decorators. Have you seen this done, or done it yourself (i.e., wrapped the two decorators into one)? I'd be very curious to see the simplest code for doing it. – eric Sep 03 '17 at 03:02
3

This behavior is caused by a feature in Webkit browsers unofficially called Page Cache, also known as the back/forward cache. It controls what happens to previous pages in the current browsing session. Webkit does something special in that it "suspends" previous pages. It's as if the previous page is hidden in another tab; clicking the back button is like bringing the tab into the foreground. The page is still there just as it was. This means no network request is made and therefore your server logic is never touched.

You'll see this behavior in Safari as well as Chrome. Look at the Network Inspector panel and watch the network traffic when you click back to a page. At a glance it looks like a request was made. Safari doesn't help dispel the notion that no request was actually made. Chrome is more polite and tells you the page was loaded "(from cache)". In Chrome, look at the size column or click the request and look at the Status Code in the Headers tab. Of course the other indicator is how long the 'request' took in the Timeline (probably 0ms).

That explains the behavior...now how to get around it. The best solution may just be a reminder on the logout page to close the browser window.

You've correctly determined that there's nothing you can do on the Django side. The cache decorators won't help you. Unfortunately there doesn't appear to be a canonical answer to preventing Page Cache from stashing a page. It also seems to be a feature in flux, so a solution now may just be a hack that won't work on later versions of Webkit. Or Firefox may create a similar feature with different implementation.

Serving your site over HTTPS with cache-control: no-store or cache-control: no-cache may do it but it's heavy handed for sure. One possible hack would be to set an unload/onunload event handler.

Read more about Page Cache behavior and the unload hack suggestion on these two Surfin' Safari articles.

UPDATE - @DigitalCake found that Cache-Control:no-store has some effect. In Django, this is accomplished with @cache_control(no_store=True) decorating the view. no store works in Chrome (v17.0.963.66) - the page is not stashed in Page Cache and the back button causes a network request. no store does not work in Safari (v5.1.3). This shows that even across Webkit browsers Page Cache is implemented differently. It demonstrates also the point that current work arounds are likely to be temporary hacks.

JCotton
  • 11,650
  • 5
  • 53
  • 59
0

I tried this solution and it worked for me.

I had put both cashe control and login required.

Here is the example

from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import cache_control

@cache_control(no_cache=True, must_revalidate=True, no_store=True)
@login_required(login_url='login')
def myview(request):
   return HttpResponse(render(request,'path_to_your_view.html'))
Mahmood
  • 31
  • 5
0

March 2020 update: Adding to the accepted answer, the Django 3.0 docs show a never_cache decorator.

It adds a Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private header to the response.

Paolo
  • 20,112
  • 21
  • 72
  • 113
mark_s
  • 466
  • 3
  • 6