Authentication is hard: there are many edge cases, and most users (including yourself) usually only go the "happy path" once and never see the edge cases. If you have rolled your own authentication, or been using older authentication solutions, or resorted to HTTP Basic Authentication, this card will tell you what to do to make your application safe.
Any application that stores sensitive data in the browser
That is: cookies, e.g. by offering a login.
- Ask the admins to turn on SSL (they will set an HSTS header for SSL-only sites)
- Make cookies secure and http_only
- Never hard-code the
http
protocol into URLs that point to your application, which makes you vulnerable to SSL-stripping Show archive.org snapshot .- When linking to internal resources, just use the path without protocol or URL
- When linking to external resources, you can make the URL start with
//
(no protocol), which makes the browser use the protocol of the requesting page - A common mistake is also to hardcode
http://
into mailer templates, where you need a protocol. See make ActionMailer use the correct protocol for links or have the current protcol as a configuration option of each environment.
- Ensure you have different session secrets for your stages
Issues with Clearance
Clearance
Show archive.org snapshot
may install routes we don't need, e.g. a sign_up_url
for internal-only sites.
: - override each route in routes.rb: match 'sign_up' => redirect('/')
(redirects to home page)
- if you don't want any of the routes from clearance-0.8: simply remove
Clearance::Routes.draw(map)
from config/routes.rb
When you're done, check your changes by running rake routes
.
handle_unverified_request
When Rails gets a request with wrong/missing CSRF-Token, it calls ApplicationController#handle_unverified_request
and continues processing the request!. Per default, the method only resets the Rails session
, but since Clearance doesn't store its session there, you should delete the remember_token
cookie.
With Clearance < 0.10.5, you need to do it yourself:
class ApplicationController < ActionController::Base
# ...
private
def handle_unverified_request
super
sign_out
end
end
Issues with basic authentication
For basic authentication, the issue is similar as above. Resetting the session has no effect as the authentication data is not stored in the session. A simple counter-measure is to crash:
def handle_unverified_request
super
raise "Access Denied."
end
Issues with home-made authentication
If possible, don't do this! It's really easy to get this wrong. Use a proven authentication library like Clearance or Devise instead.
If you really, absolutely, positively need to grow your own authentication solution, remember to consider the following:
Migrate unsalted user tables without changing existing passwords
Use salts to disarm brute force attacks.
In order to let users keep their passwords, hash passwords as before, then hash the result again with salt and only
store this to the DB.
- add a column
salt
to the users table - initialize each record with a random string
- update all stored passwords in a migration; see the attached migration (adjust model/column names)
- implement this double-hashing in creation and authentication of users
You may want to use this method:
private
def salted_hash(plain_password)
# something like has_defaults would be nicer, but fails with e.g. User.new :password => 'foo'
self.salt ||= ActiveSupport::SecureRandom.hex(20) if new_record?
# this double-hashing technique is explained in
# https://makandracards.com/makandra/15827-checklist-for-implementing-authentication
Digest::SHA1.hexdigest(old_password_hashing_method(plain_password) + salt)
end
Remembering user via cookie in an insecure fashion
The Rails session
cookie is signed, which means you cannot modify it without Rails noticing. However, if you are using the cookies
hash to store data a user must not tamper with (e.g. the ID of the logged-in user), sign it like this: cookies.signed[:user_id]
. In addition, employ the SafeCookies middleware explained in this card. It will prevent attackers from reading your cookies by malicious Javascript and prevent the user's browser from sending it over insecure HTTP connections.
Generally: prefer tokens over ids for user identification, because they allow resetting sessions and more.
When using a session store other than Rails' CookieStore
Regenerate the session id when logging in. After authentication, but before writing to the session, call reset_session
. (Know this also wipes all data from the session – which might be ok when logging in.) This prevents session fixation, where an attacker foists a session on you and thereby would be logged in when you are.