Configuring Rails 3 to use HTTPS and SSL

May 5th, 2011 at 12:45 pm • permalink46 comments

This article targets Rails 3 and Rails 3.1.

The information contained in this page might not apply to different versions.

There are several ways to force your Rails application to use SSL and the HTTPS protocol.

Rails >= 3.1

If you are using Rails 3.1 (currently available in beta1) or greater, this commit makes it incredibly easy to switch from HTTP/HTTPS and vice-versa.

Simply use config.force_ssl = true in your environment configuration.

# config/application.rb
module MyApp
  class Application < Rails::Application
    config.force_ssl = true
  end
end

You can also selectively enable https depending on the current Rails environment. For example, you might want to keep HTTPS turned off on development, and enable it on staging/production.

# config/application.rb
module MyApp
  class Application < Rails::Application
    config.force_ssl = false
  end
end

# config/environments/production.rb
MyApp::Application.configure do
  config.force_ssl = true
end

Behind the scenes, Rails adds the awesome Rack::SSL Rack middleware to your application middleware stack. Rack::SSL automatically filters the request, redirects not-HTTPS requests to the corresponding HTTPS path and applies some additional improvements to make sure your HTTPS request is secure.

Rails < 3.1

If you’re not using Rails 3.1 don’t worry. Enabling HTTPS is as easy as adding the following line to your environment configuration.

config.middleware.insert_before ActionDispatch::Static, "Rack::SSL"

Note that I’m passing Rack::SSL as string to delegate the loading of the class at the end of the Rails application initialization. Also note the middleware must be inserted in a specific position in the stack, at least before ActionDispatch::Static and ActionDispatch::Cookies.

Don’t forget to define Rack::SSL dependency in your Gemfile.

# Gemfile
gem 'rack-ssl', :require => 'rack/ssl'

Enabling HTTPS and HTTP in parallel

Transitioning an HTTP-based application to HTTPS might not be as easy as you think, especially if your application embeds assets from third-party hosts.

There are cases where you want to make your application available using both HTTP and HTTPS. For instance, we adopted this strategy in RoboDomain for a while when we moved the application to HTTPS.

Rack::SSL has a very interesting and undocumented feature. You can pass an :exclude option to determine when to enable/disable the use of HTTPS.

The following code enables Rack::SSL and all its filters only in case the request comes from a HTTPS connection.

config.middleware.insert_before ActionDispatch::Static, Rack::SSL, :exclude => proc { |env| env['HTTPS'] != 'on' }

Both the following URLs will continue to work, but the first one will trigger the Rack::SSL filters.

https://example.com

http://example.com

This solution also works very well for cloud providers like Heroku and completely replaces home-made solutions like the one I posted in this StackOverflow answer months ago, also featured here and here.

If you want to use SSL on Heroku and redirect the HTTP traffic to HTTPS, my suggestion is to go with Rack::SSL.

Filed in Programming • Tags: , , , , ,

Comments

Donny says:

Great article!
But how can I use HTTPS only on some url? For example, I only want to use HTTPS for user login but keep others with HTTP!

I only want to use HTTPS for user login but keep others with HTTP!

In this case, you might want to add a before_filter in your controller to make sure only these specific actions are redirected to the http version and use the “Enabling HTTPS and HTTP in parallel” strategy I described above.

Chris says:

I’m not sure what you mean Simon.

Do you mean putting this in a before filter? :

config.middleware.insert_before ActionDispatch::Static, Rack::SSL, :exclude => proc { |env| env['HTTPS'] != ‘on’ }

Thanks

No, I mean that you should add that code to your config but you should also add a before_filter in the controller where you want to force the ssl in order to redirect the action to the SSL version. In fact, the code you pasted above doesn’t force the SSL, it only ensures all the Rack::SSL filters are correctly enabled when a request comes from HTTPS.

class SessionController

  before_filter :force_ssl

  # login
  def new
  end

  # login
  def create
  end


  private

    def force_ssl
      if !request.ssl?
        redirect_to :protocol => 'https'
      end
    end

end
Tomás D'Stefano says:

Excelent Article!
Btw, the link for the rails commit is broken.
The right link is: https://github.com/rails/rails/commit/2c0c4d754e34b13379dfc53121a970c25fab5dae

Fixed, thanks! :)

Thanks for the article Simone, but I must say that I’ve been swayed that doing selective SSL (i.e. enabling SSL on your session or account routes only) is best enforced through the routes.rb (as opposed to with filters as it was done with the ssl gems in older Rails versions). See the following SO article for details (http://stackoverflow.com/questions/3634100/rails-3-ssl-deprecation). Any thoughts?

I believe filters and routes can be combined because they have different goals. If you enforce https in a route, it doesn’t mean the user is automatically redirected from the non-https to the https version. In fact, if the user requests a non-https route, it receives a 404 error message.

Using filters you can provide a better user experience by redirecting the user to the corresponding https-only route.

pETer says:

Great article, thanks for share!

Dan Berger says:

Thanks for this article. I am having a similar problem but in reverse. I want to configure Rails 3 and Rack to not direct all HTTP requests to HTTPS. do you know where I might do this?

You can create a Rack middleware to force the HTTP protocol.

pjammer says:

How is what you suggest here different then what https://github.com/tobmatth/rack-ssl-enforcer Rack ssl-enforcer is doing?

I couldn’t get it to work 2 months ago in Rails 3.0, as it was too enforcing for cname’d urls in a multi tenant site, however it looks similar to what you are doing.

I just didn’t know if you threw it out cause it didn’t do something you needed too.

I didn’t take it into consideration because Rails is moving to Rack::SSL and I decided to not introduce additional dependencies.

Jones Lee says:

Nice find, I’d like to ask for your advice on a good solution to replace DHH’s ssl_requirement plugin with force_ssl, especially on enforcing ssl_required and ssl_allowed for specific controllers. Cheers

Jun C says:

Nice article!

Do I need to install ssl certificate after adding config.force_ssl = true in development.rb? Received error saying “received a record that exceeded the maximum permissible length.”. Thanks.

Wrong says:

Running Rails 3.0. This doesn’t work.

config.force_ssl works only for Rails 3.1. Please see the second section for previous versions.

Rick Tessner says:

Hi,

I ran into a problem using “config.middleware.insert_before ActionDispatch::Static …” in production. The basic issue was that in my production configuration, I have:

config.serve_static_assets = false

Which, in rails 3.0.10, completely eliminated ActionDispatch::Static from the middleware stack. I found, by using the incredibly useful “rake middleware” that using

config.middleware.insert_after Rack::Lock …

worked whether or not ActionDispatch::Static was configured or not. Rack::Lock appears immediately before ActionDispatch::Static in the stack and I didn’t see any configuration options that would disable Rack::Lock.

Seems to work but I haven’t tested it fully at this point.

Rick

Good point, thanks for sharing! :)

I used it like that:

config.middleware.insert_before ActionDispatch::Cookies, Rack::SSL, :exclude => proc { |env| env['HTTPS'] != ‘on’ }

Worked like a charm. Thank you very much for sharing!

Brad says:

Do you have any tips on getting your local server (in dev) to serve up both http and https requests? We’ve only been able to get thin to work with one or the other (ie. start it with/without –ssl) but this becomes annoying if we hit links that are non ssl as they just plain don’t work. (get an Error 324 (net::ERR_EMPTY_RESPONSE))

Rafal says:

How can I force ssl only on certain controllers, and leave some without ssl?

Yes, you can do this by using a before_filter.

Rafal says:

So if I do a config.force_ssl = true on the application level, how can I disable ssl on certain controllers?

In this case, you need to use a custom middleware or a before_filter.

Akram says:

Hi Simone,

Thanks for a great article it really helped to solve lot of issues. However there is one problem which still persists.

By using before_filter I can force specific actions in a controller to use https protocol, but once I move to some other action the https protocol persists is there a way to switch from https protocol to http protocol?

Thanks

Use a before_filter to force the non-https or make sure to pass https false when you generate links.

Akram says:

Thanks Simone I ended up using before_filter to force a page from https:// to http://. I was not able to see certain links which are enabled only once the user sign’s in (I have added https here). I guess this is because I am running two instances of Nginex.

Akram says:

One more thing Simone, is it possible to have my Ajaxified pages served through https protocol and the few other pages served through http? When I implement SSL on the whole site the Ajaxified pages work well with SSL however if I implement it on few actions which have Ajaxified pages SSL seems not to work.

Any ideas about a workaround through this?

Paul says:

One of the side-effects of ‘config.force_ssl = true’ is that it sends a Strict-Transport-Security header that expires after 1 year. The Strict-Transport-Security header indicates to the browser that all requests for resources in this *entire domain* should use HTTPS until the expiration date. So, if you have multiple apps running under the same domain, this could have some serious unintended consequences that won’t go away for a very long time.

Any idea how to change the expiration date or enable ‘config.force_ssl = true’ without having it send the Strict-Transport-Security header? It looks like this can be set when Rack::SSL is instantiated, but as best as I can tell, there is no way to configure Rails so that it passes in options when instantiating Rack::SSL. I could always override Rack::SSL, but that seems ugly…

Paul says:

Sorry, guess I missed the obvious… You actually demonstrated configuring Rack::SSL in the original post, so the answer is:
config.middleware.insert_before ActionDispatch::Static, “Rack::SSL”, :hsts => false
or
config.middleware.insert_before ActionDispatch::Static, “Rack::SSL”, :hsts => { :expires => 600 }

Any chance I’ve missed any other even better ways of doing this? I thought something like this might work, but it doesn’t because middleware.swap calls Array.delete, which deletes both the old and new instance of Rack::SSL :
config.force_ssl = true
config.middleware.swap Rack::SSL, “Rack::SSL”, :hsts => false

Nathan says:

You might try config.ssl_options = {:hsts => false}

daudi says:

Excellent & helpful article; thank you! I’ve just completed migrating my app over to Rails 3.1.3 on Heroku Cedar stack. Since I have a few users with pre-SSL linkages (PayPal IPN notify_url’s, iCalendar feed url’s), I need to have https for most app access but allow in the pre-SSL linkages stored at other sites. Heroku pre-compiles assets and so some changes are needed to get the Rack::SSL middleware inclusion to work (shown below). I like having a log trace verifying the SSL inclusion in the production system. Notice that the two require statements are now required due to Heroku’s pre-compilation of asset during slug construction.

config/environments/production.rb
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
my_uname = `uname -n`.chomp # determine whoweare

unless %w(dev1 dev2 dev3).include?( my_uname )
require ‘rack/ssl’
require ‘rack-cache’

puts “>>>>>> forcing SSL for #{my_uname} <<<<< proc { |env|
# dont force SSL redirect if request is http && (transaction_notes/create OR feeds)
env['HTTPS'] != ‘on’ &&
(
( env['REQUEST_URI'] =~ /transaction_notes/ && env['REQUEST_METHOD'] =~ /POST/i ) ||
env['REQUEST_URI'] =~ /\/_feed\//
)
}

end # unless SSL not required for local machines

fearless_fool says:

Simone (et al): Great article and comments!

How do I configure things in Rails 3.1 so that http://mysite.com redirects to https://secure.mysite.com?

Specifically, I’m running Rails 3.1 on the Cedar stack on Heroku, using Hostname Based SSL. My SSL certs are set up for secure.mysite.com (and Heroku documents state that you can’t use HTTPS on a TLD anyway). My hunch is that simply calling force_ssl in my ApplicationController won’t have the right effect.

Stefan says:

One of the best posts i have read in this topic!
Keep up the good work.

I have set upp a post on setting up ssl on heroku.
Especially it covers the new Heroku SSL Product “Endpoint”,
wich i really can recomend!
http://www.stefan-kuehn.com/2012/05/setting-up-ssl-on-heroku.html

Philip Rhoades says:

I have a NON-SSL setup for a server and it works fine for current apps with the login stuff built into the Rails app itself e.g. in /etc/httpd/conf/httpd.conf:

ServerAdmin phil@xxx.yyy
DocumentRoot /var/www/html
ServerName xxx.yyy
RailsBaseURI /library

but I can’t get something like that to work with an app that DOES NOT have the login stuff built into the Rails app itself e.g. in/etc/httpd/conf.d/ssl.conf:

AuthName “tstssl”
AuthType Basic
AuthUserFile /home/secure/apasswds
AuthGroupFile /dev/null
require user phil

It seems like there should be a simple solution to this approach?

Thanks,

Phil.

Philip Rhoades says:

Sorry, I should have been clearer – the first config lines are in in a “” block and the second config lines are in a “” block.

Phil.

Philip Rhoades says:

?? Some of my text was stripped – try:

VirtualHost *:80

Directory “/var/www/ssl/tstssl”

without angle brackets . .

Phil.

Jordan Glassman says:

The ability to use the exclude option in ssl_options has been removed: https://github.com/rails/rails/pull/5515

Does anyone know why? What is supposed to replace this functionality?

If you use rack-ssl gem instead of relying on the bundled Rails version, you still have access to that feature.

Jordan Glassman says:

Success. Do you know why this was removed from Rails? It’s a pretty useful feature.

Jordan Glassman says:

Looking more closely at this, simply requiring `rack-ssl` is not enough to actually make it work. The `rack-ssl-rails` gem runs this in an initializer:

config.middleware.insert_before ActionDispatch::Static, Rack::SSL

Doesn’t it make more sense to do this:

config.middleware.swap ActionDispatch::SSL, Rack::SSL

I’m not sure why my app seems to be respecting `exclude` at this point, because I haven’t done either one.

Eduardo says:

I am developing a Rails3 application that uses SSL connection. I am currently using third party resources that are js and css files for implementing a map (OpenStreetMap) . I have already tried to import these resources (js and css) into my application, but the javascript code tries to access an external WMS via HTTP.

The problem is that Google Chrome is blocking access to third-party resources from HTTP when the application is in HTTPS.

So I disabled SSL on a certain pages of the application and tried to force the HTTP or HTTPS the way I desire.

I have already tried to do what you say un this blog and it works!

But when I force the HTTP protocol to the page where these resources will be used using Google Chrome, it forces HTTPS connection causing infinite loop.

If I clear the Chrome cache (that have already accessed the same page with HTTPS) in order access it via HTTP it works. But if I have accessed a HTTPS page and try to access via HTTP, Chrome forces the HTTPS connection resulting in an infinite loop.

The question is: Is there something I can set in the request that causes Chrome to accept the connection?

Regards,

Eduardo

Leni says:

How did you configure Rails so that root_url returns “https://blah” ?

Arihant says:

I am using rails Rails 3.0.11 and we I am following the second part f your article. I am using the gem rack ssl as specified in your comments and used the function specified in the comments to force ssl on specific controller. But I am getting this error saying Error 310 (net::ERR_TOO_MANY_REDIRECTS). Any suggestions ?
I have added this function in my application controller
def force_ssl
if Rails.env.production?
if !request.ssl?
redirect_to :protocol => ‘https’
end
end
end
and just calling before_filter :force_ssl

Add a Comment




Follow Me
    Random Quote