On Browser Caching

In this day and age, you must have ventured out onto the internet, you know what a browser is, and you have done your share of web surfing. Otherwise you wouldn't even be reading this. It always begins the same way: you point your browser to a site, a page, a URL. The page you are asking the browser to load could be long. It could contain embedded pictures. Then after a while, you may need to load the same page again. And then again, some time after that. So the mindful engineers who wrote the first browsers thought about that, and decided to store data, in order to prevent the need of having to retrieve the same data over and over again. And hence, all browsers from that point on try to cache data. This of course, can be disabled, but hardly anyone ever does that.

These improvements, like many others, come at a price. Namely, by storing data, in whatever form, browsers infringe on security and privacy and introduce a fair number of new implications.

Browser caching is a great thing. It does wonders for the user experience. It improves browser responsiveness. It speeds up retrieving pages that are frequently requested. That is, unless, you want to prevent it.

A typical browsing session may include the user logging into an application. Depending on their system permissions and privileges, users may be able to see parts of the application that are inaccessible to other users. A typical browsing session also includes moving through at least a few pages. Some users can see the some pages in the application while others may have access to different ones. The content displayed on those pages may also be tied to the permissions of the user. The majority of web applications today are not only user-centric, but provide for some form of personalized surfing and experience.

Problems Arising from Browser Caching

Once the user finishes using the application, one of the following things will likely happen: the session may time out, the user may log out (provided that the application is well thought out, properly designed and has the logout functionality accessible from anywhere) or they will simply close the browser window. In any case, it may be possible for a user with a different level of system privileges to access and redisplay pages from the browser session of the first user by simply using the Back button in the browser toolbar. If user credentials were stored in a cookie, it may be even possible for the second user to start a new browser session and regain access to the system using the credentials stored in the browser cookie. Remember how you open a browser and go back to amazon.ca? Once there, the user is greeted by the application with something like:

'Hello Stanley Kowalsky (Not Stanley? Click here).'

Of course, not all users will be called Stanley, but the browser obviously had a way of telling the application who the last user that visited that site was...

The risk of displaying information and/or application functionality exists and has significant implications from security and privacy perspective. Expecting every user to log out of an application properly is a feeble security approach, as even when users do log out, it may not be enough to prevent the re-display of cached pages within the same browser instance.

New generations of browsers introduce the concept of tabbed browsing, a new realm of concern in terms of browser cookie handling and cookie sharing between opened tabs of the same browser instance. Different browsers exhibit different behavior when it comes to cookie sharing between existing tabs of the same browser instance/window.

So obviously having a way to tell the browser not to store select pages with sensitive information would be beneficial and would also go a long way to address privacy concerns to a large extent. And there are a few ways of doing exactly that, but only one correct one.

How to Control Browser Caching

Built on the stateless HTTP protocol, when users interact with an application, they issue a series of HTTP requests and as a result of that, the application (running on a server) generates a number of HTTP responses to those requests. Each HTTP request and/or response may contain a certain number of headers. A very widely used header for cache control is the pragma:no-cache, just slap that on all responses,and... Bob's your uncle. Right? Hmmm... actually, no -- that's completely wrong.

If anything, the pragma:no-cache  is a false friend. HTTP/1.1 RFC 2616 clearly states that the meaning of the pragma:no-cache header in a server response is undefined.

This common misconception is based on the fact that the pragma:no-cache header is a request directive, not a response one. What this really means is that, if there are any intermediary points between your browser and the destination server, the pragma:no-cache directive will force all those points to pass on your request, as opposed to retrieving the response to that request from their own cache. These intermediaries could be a proxy server, a data caching appliance, a gateway, etc.

Things get interesting once the request reaches the server, at least from an application perspective. The application assumes control over the request and does the appropriate processing to serve the request. In spite of the widespread belief, inserting the pragma:no-cache in the response is not the correct way to control browser caching. According to HTTP/1.1 RFC 2616, the expected behaviour with this header in the response is undefined! Let's say that one more time and add a little translation along the way: browser behaviour and implementation to the pragma:no-cache directive is not specified by RFC 2616, or any other for that matter. And the fact that most browsers provide behaviour implementation for responses that carry this header certainly doesn't help the cause. Not only that, but the browser being used could be an earlier generation or version, or might even only support HTTP/1.0. And then we find ourselves just out of luck.

Play it (like it is) Sam!

Well, if that's not the proper way of controlling browser caching, then what is? Fortunately, the answer to that is simple: the Cache-Control general header can establish cache control along the entire request/response chain. The header itself can have multiple different directives. The no-cache, directive of the Cache-Control header is the way to tell any browser not to cache. This header is part of HTTP/1.1, which really means it can be ignored by certain (older) browsers that may implement only HTTP/1.0. To ensure content of the page (and the page itself) is not cached even in browsers implementing only HTTP/1.0, use the Expires header, setting it to a value earlier (time-wise) than the request. A typical value used for this purpose is 0. If you want to use an explicit date that would produce the same effect, you could use something like: Mon, 1 Jan 1900 00:00:00 GMT effectively telling the browser that the content it just received, has in fact long expired. This, in turn, would force the browser to really send that same request to the server, and retrieve a fresh copy.

Another distinction that needs to be made is the difference between a browser cache and history store.

Sending Cache-Control headers in server responses (to prevent browsers from caching pages) does not necessarily guarantee that those pages will not get written in the browser history on hard storage. In fact, even with Cache-Control headers in the response, it may still be possible to redisplay pages with sensitive content by using the browser navigation back button, whereby browsers retrieve these pages from the history store on disk. According to the authoritative HTTP/1.1 specification, this behavior is correct!

Different browsers react differently when requested to redisplay a page from a browsing history. Some browsers will also take into account whether a given page is accessed over http or https. To address the problem where pages with sensitive content may get written to the disk, use the no-store directive of the Cache-Control header. This will instruct the browser to try not to write the given page to the disk. The must-revalidate directive often used in conjunction with the two above. The must-revalidate directive instructs the cache not to store the response, even when a browser cache is configured to ignore the Expires directive. It forces the browser to revalidate that response with the origin server each time.

Here then, is the complete set of headers that will allow you to control whether a browser ends up caching served content:

Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: must-revalidate
Expires: 0

Now there are many presentation layer technologies and/or frameworks in use today. Whether your application is built using plain JSPs, PHP, Struts tiles, the Spring Web Flow framework or something else, the headers from the snippet above need to make their way into each and every page that you want to prevent the browser from caching. And forget pragma:no-cache, it's time to make new friends.

Comments for this article have been closed

The crew behind ASOT

We're a team of interactive, software, and business intelligence experts skilled in the design, construction, and management of online enterprise systems.

Visit The Jonah Group site

Get in touch with us