Fixing authentication in legacy applications
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.
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
httpprotocol into URLs that point to your application, which makes you vulnerable to .
- 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
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_in' => redirect('/') (redirects to home page)
- if you don't want any of the routes from clearance-0.8: simply remove
When you're done, check your changes by running
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
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
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
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:
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
saltto 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
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:
Generally: prefer tokens over ids for user identification, because they allow resetting sessions and more.
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.