ASP.NET MVC Tip #40 - Don’t Cache Pages that Require Authorization
In this tip, Stephen Walther warns you to avoid making a mistake that can result in private data being displayed to unauthorized users.
Imagine that you are building a financial services website. Each user of the website can log in and view a page that lists their current investments. For example, the investments page might show a report displaying how much you have invested in different stocks, bonds, and mutual funds.
Now, imagine that your website becomes wildly successful. It is used by billions of users every day. Your website performance starts to degrade. Each time a user visits their personalized investments page, a complicated query must be performed against the database which makes the investments page slow. What do you do?
Here are some options:
Option #1 – Go hide in a cave somewhere.
Option #2 – Enable page output caching for the investments page.
Option #3 – Buy more server hardware.
Close your eyes and think about your answer for a moment. No cheating! No reading ahead! I’m patient. I’ll wait right here.
Okay, this was a trick question. If you picked Option #2 then you have just done something horribly wrong. Shame on you! Let me explain.
You can enable output caching for any page within an ASP.NET Web Forms website by adding the following directive to the top of the page:
<%@ OutputCache Duration="60" VaryByParam="none" %>
This directive caches the page for 60 seconds so that the content of the page does not need to be generated again for each user.
Within an ASP.NET MVC application, you can use the OutputCache attribute on a controller action like this:
[OutputCache(Duration = 60)] public ActionResult Index() { var investments = from i in _dataContext.Investments select i; return View("Index", investments); }
This OutputCache attribute caches the controller response to the browser request for 60 seconds.
Using the OutputCache directive or OutputCache attribute to cache the results of a request dramatically improves the performance of your website. When you enable caching, your database server does not need to regenerate the investments report page with each browser request.
Unfortunately, however, when you cache a page, the page is cached for all visitors to the website. Caching the investments page can enable one user to see another user’s private financial data.
Imagine that Joe is the first person to view the investments page. The page displays Joe’s private financial data. You’ve enabled output caching so the page is cached in memory.
Now, imagine that Mike requests the same investments page with the 60 second time interval. In that case, Mike will see Joe’s private financial data. Mike will see the private data rendered from the server cache.
Therefore, never (ever, ever) cache a page when the page contains data that is private to a particular user. If you cache the page, then the private data will be displayed to anyone who visits the website.
In general, you should only enable caching for a page when the page does not require authorization. Normally, you require authorization for a page when you display personalized data in the page. Since you don’t want personalized data to be shared among multiple users, don’t cache pages that require authorization.
Within an ASP.NET MVC application, never add both the [Authorize] and [OutputCache] attribute to the same controller action:
[Authorize] [OutputCache(Duration = 60)] public ActionResult Index() { var investments = from i in _dataContext.Investments select i; return View("Index", investments); }
In this code, you are combining caching with per-user authorization. Most likely, if you are adding both the Authorize and OutputCache attribute to the same controller action then you are broadcasting private user data to the entire world. This code is a very fragrant security smell. Don’t do it!