This blog is part of our Rails 5 series.

Adding HTTPS support is one of the first steps towards enhancing the security of a web application.

Even when a web app is available over https, some users may end up visiting the http version of the app, losing the security https provides.

It is important to redirect users to the https URLs whenever possible.

Forcing HTTPS in Rails

We can force users to use HTTPS by setting config.force_ssl = true.

If we look at Rails source code, we can see that when we set config.force_ssl = true, a middleware ActionDispatch::SSL, is inserted into our app’s middleware stack :

if config.force_ssl
  middleware.use ::ActionDispatch::SSL,config.ssl_options
end

This middleware, ActionDispatch::SSL is responsible for doing three things :

  1. Redirect all http requests to their https equivalents.

  2. Set secure flag on cookies to tell browsers that these cookies must not be sent for http requests.

  3. Add HSTS headers to response.

Let us go through each of these.

Redirect all http requests to their https equivalents

In Rails 5, we can configure the behavior of redirection using the redirect key in the config.ssl_options configuration.

In previous versions of Rails, whenever an http request was redirected to https request, it was done with an HTTP 301 redirect.

Browsers cache 301 redirects. When forcing https redirects, if at any point we want to test the http version of the page, it would be hard to browse it, since the browser would redirect to the https version. Although this is the desired behavior, this is a pain during testing and deploying.

Rails 5 lets us specify the status code for redirection, which can be set to 302 or 307 for testing, and later to 301 when we are ready for deployment to production.

We can specify the options for redirection in Rails 5 as follows :

...
  config.force_ssl = true
  config.ssl_options = {  redirect: { status: 307, port: 81 } }
...

If a redirect status is not specified, requests are redirected with a 301 status code.

There is an upcoming change to make the status code used for redirecting any non-GET, non-HEAD http requests to 307 by default.

Other options accepted by ssl_options under redirect key are host and body .

Set secure flags on cookies

By setting the Secure flag on a cookie, the application can instruct the browser not to send the cookie in clear text. Browsers which support this flag will send such cookies only through HTTPS connections.

Setting secure flag on cookies is important to prevent cookie hijacking by man in the middle attacks.

In case of a “man in the middle” attack, the attacker places oneself between the user and the server. By doing this, attacker aims to collect cookies which are sent from user to server on every request. However, if we mark the cookies with sensitive information as Secure, those cookies won’t be sent on http requests. This ensures that the browser never sends cookies to an attacker who was impersonating the webserver at an http end point.

Upon enabling config.force_ssl = true, the ActionDispatch::SSL middleware sets the Secure flag on all cookies by default.

Set HSTS Headers on Responses

HSTS or “HTTP Strict Transport Security” is a security enhancement by which applications can specify themselves as HTTPS-only to complying browsers.

HSTS capabilities of a browser can be used by sending appropriate response headers from the server. When a domain is added to the HSTS list of a browser, the browser redirects to the https version of the URL without the help of the server.

Chrome maintains an HSTS Preload List with a list of domains which are hardcoded into chrome as HTTPS only. This list is also used by Firefox and Safari.

Rails 5 has a configuration flag to set the preload directive in the HSTS header and can be used as follows :

config.ssl_options = { hsts: { preload: true } }

We can also specify a max-age for the HSTS header.

Rails 5 by default sets the max-age of HSTS header to 180 days, which is considered as the lower bound by SSL Lab’s SSL Test . This period is also above the 18 week requirement for HSTS max-age mandated for inclusion in browser preload list.

We can specify a custom max-age by :

  config.ssl_options = { hsts: { expires: 10.days } }

In Rails 5, if we disable HSTS by setting :

config.ssl_options = { hsts: false }

Rails 5 will set the value of expires header to 0, so that browsers immediately stop treating the domain as HTTPS-only.

With custom redirect status and greater control over the HSTS header, Rails 5 lets us roll out HTTPS in a controlled manner, and makes rolling back of these changes easier.