How to prevent browser page caching in Rails

Ruby on-RailsRubyApacheCachingHttp Headers

Ruby on-Rails Problem Overview


Ubuntu → Apache → Phusion Passenger → Rails 2.3.

The main part of my site reacts to your clicks. So, if you click on a link, it will send you on to the destination, and instantly regenerate your page.

But, if you hit the back button, you don't see the new page. Unfortunately, it's not showing up without a manual refresh; it appears the browser is caching it. I want to make sure the browser does not cache the page.

Separately, I do want to set far-future expiration dates for all my static assets.

What's the best way to solve this? Should I solve this in Ruby on Rails? Apache? JavaScript?


Alas. Neither of these suggestions forced the behavior I'm looking for.

Maybe there's a JavaScript answer? I could have Ruby on Rails write out a timestamp in a comment, and then have the JavaScript code check to see if the times are within five seconds (or whatever works). If yes, then fine, but if no, then reload the page?

Do you think this would work?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

I finally figured this out - http://blog.serendeputy.com/posts/how-to-prevent-browsers-from-caching-a-page-in-rails/ in application_controller.rb.

After Ruby on Rails 5:

class ApplicationController < ActionController::Base

  before_action :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end

Ruby on Rails 4 and older versions:

class ApplicationController < ActionController::Base

  before_filter :set_cache_headers

  private

  def set_cache_headers
    response.headers["Cache-Control"] = "no-cache, no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
  end
end

Solution 2 - Ruby on-Rails

Use:

expires_now()

expires_now()

Solution 3 - Ruby on-Rails

I have used this line with some success in the controller. It works in Safari and Internet Explorer, but I haven't seen it work with Firefox.

response.headers["Expires"] = "#{1.year.ago}"

For your second point, if you use the Ruby on Rails helper methods like

stylesheet_link_tag

and leave the default settings on your web server, the assets are typically cached pretty well.

Solution 4 - Ruby on-Rails

The cleaner way would be to write Rack middleware, which changes the Cache-Control header based on some logic (for example, only for application/xml mime-type).

Or, for an uglier, but still working approach, one could change the ActionDispatch::Response::DEFAULT_CACHE_CONTROL constant to 'no-cache'.

Of course, if the controller and/or action granularity is required, then it's better to do this in the controller.

Solution 5 - Ruby on-Rails

Point of note: You can't conditionally clear the cache (like if a before_filter only calls reset_cache if the user's already been there). You need to unconditionally clear the cache, because the browser won't make a new request just to see if this time it needs to reload, even though it didn't need to last time.

Example:

before_filter :reset_cache, if: :user_completed_demographics?

won't work to prevent users from coming back after they've been there, since the browser uses the original cache headers on the Back button.

before_filter :reset_cache

will work, however (after refreshing the page and clearing the cache from before you added this, obviously), since, on the first request, the browser will get the no-cache, no-store, ... and apply it to future page loads.

Solution 6 - Ruby on-Rails

no_cache_control Gem.

If you need to do this for all responses, e.g., to pass a penetration test (Burp Suite, Detectify, etc.), you can install this Gem on Ruby on Rails 4+ in order to add the following headers to all responses:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: -1

It works like a charm and is really the right way to go for secure, HTTPS web applications that require authentication to do anything.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJason ButlerView Question on Stackoverflow
Solution 1 - Ruby on-RailsJason ButlerView Answer on Stackoverflow
Solution 2 - Ruby on-RailsSacreView Answer on Stackoverflow
Solution 3 - Ruby on-RailserikView Answer on Stackoverflow
Solution 4 - Ruby on-RailsRomanView Answer on Stackoverflow
Solution 5 - Ruby on-RailsJoshua Grosso Reinstate CMsView Answer on Stackoverflow
Solution 6 - Ruby on-RailsJoshua PinterView Answer on Stackoverflow