Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent browser page caching in Rails

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

After 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

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

use:

expires_now()

http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-expires_now


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 the rails helper methods like

stylesheet_link_tag

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


The cleaner way would be to write a 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.


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.


no_cache_control Gem.

If you need to do this for all responses, e.g. to pass a penetration test (BURP, Detectify, etc.), you can install this Gem 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

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