Secure Session Cookie in Rails over HTTPS
The secure flag can be set by an application server when sending a cookie within an HTTP response. By setting the secure flag, an HTTP client — such as a web browser — prevents cookie transmission unless the response is securely encrypted over HTTPS.
However, many web applications redirect http://
to https://
, and many Ruby on Rails applications are fronted by a web server such as Ngnix or Apache. Often, HTTPS
is terminated at the Nginx/Apache layer. Given such an architecture, consider the following problematic behavior through which a Ruby on Rails session cookie could be transmitted insecurely in clear text:
- user types
http://example.com
into browser address bar - browser makes request to
http://example.com
- browser receives 301 response w/
https://example.com
specified asLocation
header; no session cookie is present/set - browser makes request to
https://example.com
- browser receives response with session cookie present/set; the secure flag is absent from the cookie
- user types
http://example.com
into browser address bar - browser makes request to
http://example.com
- browser receives 301 response with
https://example.com
specified asLocation
header; step #5 session cookie is present/set was transmitted in clear text because the secure flag was absent in step #5.
In Rails, calling Rails.application.config.session_store
with secure: true
in config/initializers/session_store.rb
informs the Rails application to add the secure flag, but Rails will only do so if SSL is terminated at the application or if the Rails-fronting web server at which SSL is terminated – Nginx or Apache in the above example — adds an X-Forwarded-Proto
header whose value is https
.
For example, to do so in Apache, add the following to the Apache config file controlling your site:
RequestHeader set X-Forwarded-Proto "https"
And add the following to your Rails app’s config/initializers/session_store.rb
:
Rails.application.config.session_store :cookie_store,
:key => '_your_app_name_session',
:secure => ENV['RAILS_ENV'] != 'development'
Note that this requires an application restart to take effect.