Rails: Flagging all cookies as secure-only to pass a security audit
Why secure-only cookies used to be necessary
Cookies
Archive
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
Archive
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.
Rails: 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
:
Copy# 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
:
Copyrequire 'middleware/secure_cookies' config.middleware.insert_after ActionDispatch::Static, Middleware::SecureCookies
Add a test to spec/requests/secure_cookies_spec.rb
:
Copydescribe 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.
makandra has been working exclusively with Ruby on Rails since 2007. Our laser focus on a single technology has made us a leader in this space.