views:

2877

answers:

13

Our investigations have shown us that not all browsers respect the http cache directives in a uniform manner.

For security reasons we do not want certain pages in our application to cached, ever, by the web browser. This must work for at least the following browsers:

  • Internet Explorer versions 6-8
  • FireFox versions 1.5 - 3.0
  • Safari version 3
  • Opera 9

Our requirement came from a security test. After logging out from our website you could press the back button and view cached pages.

+2  A: 

Setting the modified http header to some date in 1995 usually does the trick.

Here's an example:

Expires: Wed, 15 Nov 1995 04:58:08 GMT
Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
Cache-Control: no-cache, must-revalidate
Anders Sandvig
voted down cos 1995 is not in 1950 :-)
Simon_Weaver
+3  A: 

After a bit of research we came up with the following list of headers that seemed to cover most browsers:

In ASP.NET we added these using the following snippet:

Response.ClearHeaders(); 
Response.AppendHeader("Cache-Control", "no-cache"); //HTTP 1.1
Response.AppendHeader("Cache-Control", "private"); // HTTP 1.1
Response.AppendHeader("Cache-Control", "no-store"); // HTTP 1.1
Response.AppendHeader("Cache-Control", "must-revalidate"); // HTTP 1.1
Response.AppendHeader("Cache-Control", "max-stale=0"); // HTTP 1.1 
Response.AppendHeader("Cache-Control", "post-check=0"); // HTTP 1.1 
Response.AppendHeader("Cache-Control", "pre-check=0"); // HTTP 1.1 
Response.AppendHeader("Pragma", "no-cache"); // HTTP 1.0 
Response.AppendHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT"); // HTTP 1.0

Found from: http://forums.asp.net/t/1013531.aspx

Edward Wilde
Answered your own question in three minutes. Congrats! That must be a stackoverflow.com record.
Stu Thompson
What the hell is up with the date "Mon, 26 Jul 1997 05:00:00 GMT"? Why is everybody using the exact same "date in the past"?
bart
+1  A: 

The RFC for HTTP 1.1 says the proper method is to add an HTTP Header for:

Cache-Control: no-cache

Older browsers may ignore this if they are not properly compliant to HTTP 1.1. For those you can try the header:

Pragma: no-cache

This is also supposed to work for HTTP 1.1 browsers.

Chris Dail
The spec indicates that the response must not be reused without revalidation. It is the Cache-Control:no-store which is the official method to indicate that the response not even be stored in a cache in the first place.
AnthonyWJones
+7  A: 

The PHP documentation for the header function has a rather complete example (contributed by a third party):

    header('Pragma: public');
    header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");                  // Date in the past   
    header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');
    header('Cache-Control: no-store, no-cache, must-revalidate');     // HTTP/1.1
    header('Cache-Control: pre-check=0, post-check=0, max-age=0');    // HTTP/1.1
    header ("Pragma: no-cache");
    header("Expires: 0");
Cd-MaN
This is obviously wrong. Second calls to header() for Expires, Cache-control and Pragma completely overwrite previously set values.
porneL
+2  A: 

And turn off Firebug network monitoring unless you want to pull all the hair out of your head.

leppie
A: 

I've had best and most consistent results across all browsers by setting Pragma: no-cache

petr k.
+2  A: 

The use of the pragma header in the response is a wives tale. RFC2616 only defines it as a request header

http://www.mnot.net/cache_docs/#PRAGMA

Dave Cheney
+5  A: 

DISCLAIMER: I strongly suggest reading @BalusC's answer. After reading the following caching tutorial: http://www.mnot.net/cache_docs/ (I recommend you read it, too), I believe it to be correct. However, for historical reasons (and because I have tested it myself), I will include my original answer below:


I tried the 'accepted' answer for PHP, which did not work for me. Then I did a little research, found a slight variant, tested it, and it worked. Here it is:

header('Cache-Control: no-store, private, no-cache, must-revalidate');     // HTTP/1.1
header('Cache-Control: pre-check=0, post-check=0, max-age=0, max-stale = 0', false);  // HTTP/1.1
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');                  // Date in the past  
header('Expires: 0', false); 
header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');
header ('Pragma: no-cache');

That should work. The problem was that when setting the same part of the header twice, if the false is not sent as the second argument to the header function, header function will simply overwrite the previous header() call. So, when setting the Cache-Control, for example if one does not want to put all the arguments in one header() function call, he must do something like this:

header('Cache-Control: this');
header('Cache-Control: and, this', false);

See more complete documentation here.

Steven Oxley
This is full of myths. pre-check and post-check are IE-only, relevant only for cached responses, and 0 value is a no-op.max-stale is proxy request header, not server response header.Expires accepts only single value. More than one will cause this header to be ignored.
porneL
@porneL, will you be submitting a competing answer that deals with these myths correctly?
Oddthinking
Holy headers batman!
Chad Grant
@Oddthinking, looks like http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers/2068353#2068353 is a competing answer.
Mike Ottum
This is just random guessing and shooting in the dark. Please don't do that. Lot of fields are completely irrelevant.
BalusC
+3  A: 

These directives does not mitigate any security risk. They are really intended to force UA's to refresh volatile information, not keep UA's from being retaining information. See this similar question. At the very least, there is no guarantee that any routers, proxies, etc. will not ignore the caching directives as well.

On a more positive note, policies regarding physical access to computers, software installation, and the like will put you miles ahead of most firms in terms of security. If the consumers of this information are members of the public, the only thing you can really do is help them understand that once the information hits their machine, that machine is their responsibility, not yours.

Dustman
+1  A: 

In addition to the headers consider serving your page via https. Many browsers will not cache https by default.

Harry
A: 

Use POST instead of GET. That should fix most issues.

And yes, that implies using forms instead of plain links.

bart
I'm sorry, WHAT? How's this related to anything.
the_drow
How it relates is that in the absence of any cache-headers, POST results don't get cached. http://www.ietf.org/rfc/rfc2616.txt, section 9.5. "Responses to this method [POST] are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields". The problem is POST is for creating content so it's an abuse of POST.
Tony Lee
+4  A: 

I found that all of the answers on this page still had problems. In particular, I noticed that none of them would stop IE8 from using a cached version of the page when you accessed it by hitting the back button.

After much research and testing, I found that the only two headers I really needed were:

Cache-Control: no-store
Vary: *

For an explanation of the Vary header, check out http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6

On IE6-8, FF1.5-3.5, Chrome 2-3, Safari 4, and Opera 9-10, these headers caused the page to be requested from the server when you click on a link to the page, or put the URL directly in the address bar. That covers about 99% of all browsers in use as of Jan '10.

On IE6, and Opera 9-10, hitting the back button still caused the cached version to be loaded. On all other browsers I tested, they did fetch a fresh version from the server. So far, I haven't found any set of headers that will cause those browsers to not return cached versions of pages when you hit the back button.

Update: After writing this answer, I realized that our web server is identifying itself as an HTTP 1.0 server. The headers I've listed are the correct ones in order for responses from an HTTP 1.0 server to not be cached by browsers. For an HTTP 1.1 server, look at BalusC's answer.

Chris Lindsay
+15  A: 

After a little astonishment of all the shooting in the dark in this topic, here's my contribution:

The correct minimum set which works in all of the mentioned browsers is the following:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

The PHP way would look like:

header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.
header('Expires: 0'); // Proxies.

The Java/Servlet way would look like:

response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.

The ASP.NET way would look like:

Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
Response.AppendHeader("Pragma", "no-cache"); // HTTP 1.0.
Response.AppendHeader("Expires", "0"); // Proxies.

The Cache-Control is per the HTTP 1.1 spec for clients (and implicitly required by some browsers next to Expires), the Pragma is per the HTTP 1.0 spec for clients and proxies and Expires is per the HTTP 1.1 spec for clients and proxies.

Other Cache-Control parameters are irrelevant if the abovementioned three are specified. The Last-Modified header is only intersting if you actually want to cache the request.

Note that when you include the headers both programmatically and inside the HTML page as <meta> tags, then the ones which are programmatically specified will get precedence above the ones on the same field in the HTML <meta> tags, because they appears directly in the response headers instead of the response body.

BalusC
This does not appear to be complete. I tried this solution on IE 8 and found that the browser will load a cached version when you hit the back button.
Mike Ottum
Likely your testing methodology was wrong. Maybe the page was already in the cache? Maybe the headers were incorrect/overriden? Maybe you were looking at the wrong request? Etc..
BalusC