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!

Discussion

  1. haacked says:

    I think you’re conflating two separate issues here. First, the OutputCache attribute issue appears to be ab ug in our implementation that we’re looking at.

    Blanketly saying you shouldn’t cache authenticated content in ASP.NET is not exactly correct. In ASP.NET MVC Preview 5, I would agree with you, but that’s because of a bug we just identified, not because it should blanketly never happen.

    But in regular ASP.NET there’s many scenarios for caching authenticated content. Perhaps I’m caching the same content for all authenticated users. Or maybe I’m using “donut caching”. With normal ASP.NET, this all works because it runs the authentication phase before the caching phase.

    With MVC, for example, you can still use URL Authorization (if you’re careful) with OutputCaching and it all works together properly.

    The issue around user specific content is completely a separate issue. You can cache user content if you vary the cache by user. Maybe you have a small user base with an expensive calculation to render the page.

    Also keep in mind, this isn’t even an authentication issue. Some sites show info particular to a user without authentication such as the sites that ask you if you want to meet other singles near your city. Caching that wouldn’t make sense unless you varied the cache by user or by location. No authentication is involved in that scenario.

  2. HeartattacK says:

    You’ve been haacked (a bad thing?)!!

  3. http:// says:

    Haacked seems ill…maybe better not to post on a Friday night? What does “Blanketly” mean?

  4. http:// says:

    You could use VaryByCustom and override GetVaryByCustomString to enable safe output cache for users

  5. http:// says:

    Anders – true, but if you start output caching per-user content, your cache is likely to fill so quickly that it becomes useless. The scenario for output caching privileged content really needs coarser granularity; e.g. per role.

  6. Keep in mind that caching on the client (cache-control: private) is not the same as caching on the server (OutputCache) or caching for third-party public servers (cache-control:public). Often you want to tell *clients* to cache items privately for any authenticated content, but you do not want public servers to cache this content.

    As for whether you use ASP.NET/MVC to create a server-side cached copy of the data, that’s a completely different story.

  7. http:// says:

    @Mike — good point! Not caching on the server and only caching on the client (with cache-control:private) would be a good way to improve performance without introducing the danger of leaking private data across users. And, using the OutputCache NoStore property would make the situation even safer.

  8. http:// says:

    Of course you can cache an authenticated page, just use a better cache key – e.g. a CustomCacheKey which you then fill in with the authenticated user’s ID. VaryByParam=”None” is obv the wrong way to derive a cache key.

  9. Paul Jones says:

    Good post Stephen! However in my opinion ASP.NET Cache’s problems go deeper than simply having security lapses. Now don’t get me wrong for single server environments it can do wonders, but as you start going into larger environments, it cannot scale.

    For more information on all the various bottlenecks please visit my blog or check out: http://www.alachisoft.com/ncache/asp-net-cache.html

  10. http:// says:

    I cannot agree with you.
    In the MVC Beta, it is ok to use [Authorize] and [OutputCache] attribute on the same action.
    below is the code from mvc framwork

    AuthorizationContext authContext = InvokeAuthorizationFilters(methodInfo, filterInfo.AuthorizationFilters);
    if (authContext.Cancel) {
    // not authorized, so don’t execute the action method or its filters
    InvokeActionResult(authContext.Result ?? EmptyResult.Instance);
    }
    else {
    ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(methodInfo, parameters, filterInfo.ActionFilters);
    InvokeActionResultWithFilters(postActionContext.Result, filterInfo.ResultFilters);
    }

    And i have tested for your situation, and it work well.

  11. Great Tips! Really very helpful for me. Thanks for sharing such a useful tips.

  12. vr I tried to mock a call to a Linq to SQL query, but I am struggling.

  13. seo news says:

    Thanks for sharing such a useful tips.

  14. education says:

    Congrats for the GREAT work.

  15. Great article, though – thanks.

  16. seo india says:

    Thanks for sharing the this code.

  17. seo says:

    It’s looks more like Subsonic query.

  18. gifts says:

    Better examined some potential.

  19. travel says:

    Very useful information given about MVC.

  20. health says:

    Nice, Keep up the good work!

  21. keane says:

    thought Salt Lake would make the list! (Awesome outdoorsy stuff, people, you should seriously google “Southern Utah Red Rock” pics and then rearrange all your travel plans to make a stop here.)Online High School | Homeschool

  22. very nice post thanks!!!

    i really like it
    keep working like that

  23. Buy Viagra says:

    Buy Buy Viagra in a place that gives us a exepcional service, with excellent warranties and prices when ordering pharmacy viagra visit xl and very satisfiedine[/url] without prescription and get just what you are looking for from a reliable online pharmacy: superior quality products, unbeatable prices, effectiveness, safety, discretion, fast shipping, and total satisfaction guaranteed.

  24. Be Creative says:

    interestng post.. thanks!!!

  25. veryy nice post thanks!!!

  26. NikeAir says:

    Nice article, very helpful. Thanks!—
    Nike ,Jodon ,kappa shoes are selling on the way..Come on
    …………….Nike shoes ! Addidas ! crazy buying —————- Nike Shoes || air yeezy

  27. Nathanial says:

    Better support for database should not be over-used but at the same time, it is a required feature.

    free ads |employment |sleep number bed

  28. klip izle says:

    You could use VaryByCustom and override GetVaryByCustomString to enable safe output cache for users

  29. HD Video Converter says:

    As the users of HD Camcorders like Sony, Canon, Panasonic, this HD Video Converter is necessary to help us convert hd Video easily and quickly. The Converter for HD provides several practical editing functions to help you achieve ideal output effect. Trim function is to cut videos into clips which you can just convert and transfer to your player. Crop function helps you remove black bars around the movie. You could use Effect function to adjust video brightness, contrast, saturation and more parameters. More powerful and considerate functions are waiting for you to explore.MKV Converter l FLV Converter l DVD Ripper @