Creating a secure User model in Rails (Part 2)
March 2, 2009
In the last post I discussed why I wanted a secure user model, and the design for the database. This post one (hopefully) secure implementation.
Design Time
The requirements for this aren’t particularly high, but here is the basics:
- When being created, a password and password confirmation must be supplied, which must match
- When being created, the password is hashed and saved in the hashed_password field
- When begin updated, if a password is supplied, a password confirmation must be supplied, which must match
- When being updated, if a password is supplied, it is hashed and saved in the hashed_password field
- The password and password_confirmation fields should be blanked after they are used, and should not refer to fields in the database
- The password must be at least 4 characters
Code Time
Ok, so we have got some simple requirements, so lets get coding. First of all the not-real-but-do-exist password and password_confirmation fields, these can be created using accessor methods:
attr_accessible :display_name, :email, :password, :password_confirmation attr_accessor :password, :password_confirmation
If you aren’t using attr_accessible you should be! Only the attributes specified can be set via mass assignment. You may wonder why I am not using the attr_writer method from Ruby, not Rails, which makes the attribute write-only (a page on Ruby accessors is available in the RubyDocs)? Well, in order for the validations to work they need to be able to read these, which is kind of annoying. Custom validations could probably be written, but we can just set these to nil when they are no longer needed.
Next up we need some validations:
validates_length_of :password, :minimum => 4, :if => Proc.new { |user| !user.password.blank? } validates_confirmation_of :password, :if => Proc.new{ |user| !user.password.blank? } validates_presence_of :password, :on => :create validates_presence_of :password_confirmation, :if => Proc.new{ |user| !user.password.blank? }
The validates_confirmation_of will automatically look for an attribute postfixed with _confirmation, and check this is the same. The bottom two seem a bit weird, after all, we are already checking to see that the password is at least 4 characters, and that the confirmation matches the password, right? Well no, if an attribute is blank, then any other validations will not be run, so we need to check for that too.
Finally we need some code to actually encrypt the password:
before_save :generate_password_hash, :if => Proc.new{ |user| !user.password.blank? } protected def generate_password_hash # salt is a digest of the time and email, mixed up randomly self.salt = Digest::SHA1.hexdigest("#{Time.now.to_s}#{email}".split(//).sort_by {rand}.join) self.hashed_password = encrypt(@password, self.salt) # clear password and confirmation @password = @password_confirmation = nil end def encrypt(salt, password) Digest::SHA1.hexdigest(salt + password) end
This should be fairly explanatory, before a record is saved, if the password isn’t blank run the generate_password_hash function. This first creates a salt, then encrypts the password using this, and finally clears the password and password confirmation. One important think to note if doing this yourself is the mixture of self and @ before attributes. If you want to read an attribute you can just specific its name, if you want to write to a real attribute you have to prefix it with self, and if you want to write to a virtual attribute you need to prefix it with @ – fun…..!
So there you have it, a (hopefully) nice and secure User model! If you can see any blatent issues, please post a comment and I shall take another look! You can download the complete model if you are too lazy to copy and paste! In the next post I will look at creating Rspec tests for this!