Why secure-only cookies used to be necessary
Cookies
Show archive.org snapshot
have an optional secure
flag. It tells the browser to not send the cookie for a non-https request.
It used to be important to activate the secure
flag even on sites that automatically redirect users from http://
to https://
. The reason was that most users will only enter a scheme-less domain like makandra.de
into their location bar, which will default to http://makandra.de
in any browser. Even though the site will immediately redirect to https://makanda.de
, cookies from a prior visit will already have appeared on the unencrypted wire during the first request to http://makandra.de
.
Why secure-only cookies are no longer necessary
Today, all sites should use https://
. They should also set a
HSTS header
Show archive.org snapshot
which makes browsers default to https://
instead of http://
when the user enters a scheme-less domain into their browser's URL bar.
For a page that exclusively uses https://
with HSTS, it is not necessary to set the secure flag on your cookies. There is simply no case when the browser would talk to the server via unencrypted http://
requests.
Why you might need secure-only cookies anyway
A security audit will still raise missing "secure" flags as an issue that needs to be fixed.
It's usually easier flag all your cookies as secure-only, than it is to explain why your application does not need secure cookies.
Option 1: ActionDispatch::SSL middleware
You can use the
ActionDispatch::SSL middleware
Show archive.org snapshot
to automatically set the secure
flag on cookies. This middleware can be enabled by configuring the following in your application.rb
.
config.force_ssl = true
If you are using a load balancer then you most likely want to use this in combination with the ActionDispatch::AssumeSSL middleware Show archive.org snapshot (Rails >= 7.1).
This middleware makes your app assume that SSL terminates at the load balancer and all requests are arriving via SSL. It changes the redirect and cookie security target to HTTP instead of HTTPS.
config.assume_ssl = true
Option 2: Use a custom middleware to automatically flag all cookies as secure-only
In a Ruby on Rails app you can add a middleware that automatically sets the Secure
flag to all server-set cookies. The flag is only added for secure requests, so cookies will still work for local development where you might still use http://
.
If you set any cookies from JavaScript, this isn't fixed by the middleware.
Add this to lib/middleware/secure_cookies.rb
:
# On HTTPS requests, we flag all cookies sent by the application to be "Secure".
#
module Middleware
class SecureCookies
COOKIE_SEPARATOR = "\n".freeze
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if headers['Set-Cookie'].present? && Rack::Request.new(env).ssl?
cookies = headers['Set-Cookie'].split(COOKIE_SEPARATOR)
cookies.each do |cookie|
next if cookie.blank?
next if cookie =~ /;\s*secure/i
cookie << '; Secure'
end
headers['Set-Cookie'] = cookies.join(COOKIE_SEPARATOR)
end
[status, headers, body]
end
end
end
Add this to your Middleware stack in the middle of config/application.rb
:
require 'middleware/secure_cookies'
config.middleware.insert_after ActionDispatch::Static, Middleware::SecureCookies
Add a test to spec/requests/secure_cookies_spec.rb
:
describe Middleware::SecureCookies do
it 'flags all cookies sent by the application as secure' do
get 'https://www.example.com/test/set_cookie'
response.headers['Set-Cookie'].should =~ %r(test=\S+; path=/; Secure$)
end
it 'will not flag cookies as secure when HTTPS is not being used (in development and tests)' do
get 'http://www.example.com/test/set_cookie'
response.headers['Set-Cookie'].should include('test=') # Cookie is still set
response.headers['Set-Cookie'].should_not include('; Secure')
end
end
Note that /test/set_cookie
must be an existing route that sets a cookie.