Storing Hashed User Passwords in the Database

Problem

The database table defined in Recipe 15.8 stores users passwords as plain text. This is a bad idea: if someone compromises the database, she will have all of your users passwords. Its best to store a secure hash of the password instead. That way, you don have the password (so no one can steal it), but you can verify that a user knows his password.

Solution

Recreate the users table from Recipe 15.8 so that instead of a password field, it has a hashed_password field. Heres some MySQL code to do that:

	use mywebapp_development;
	DROP TABLE IF EXISTS users;
	CREATE TABLE users (
	 id INT(11) NOT NULL AUTO_INCREMENT,
	 username VARCHAR(255) NOT NULL,
	 hashed_password VARCHAR(40) NOT NULL,
	 PRIMARY KEY (id)
	);

Open the file app/models/user.rb created in Recipe 15.8, and edit it to look like this:

	require sha1
	
	class User < ActiveRecord::Base
	 attr_accessor :password
	 attr_protected :hashed_password
	 validates_uniqueness_of :username
	 validates_confirmation_of : 
password,
	 :if => lambda { |user| user.new_record? or not user.password.blank? }
	 validates_length_of :password, :within => 5..40,
	 :if => lambda { |user| user.new_record? or not user.password.blank? }

	 def self.hashed(str)
	 SHA1.new(str).to_s
	 end

	 # If a user matching the credentials is found, returns the User object.
	 # If no matching user is found, returns nil.
	 def self.authenticate(user_info)
	 user = find_by_username(user_info[:username])
	 if user && user.hashed_password == hashed(user_info[:password])
	 return user
	 end
	 end

	 private
	 before_save :update_password

	 # Updates the hashed_password if a plain password was provided.
	 def update_password
	 if not password.blank?
	 self.hashed_password = self.class.hashed(password)
	 end
	 end
	end

Once you do this, your application will work as before (though youll have to convert any preexisting user accounts to the new password format). You don need to modify any of the controller or view code, because the User.authenticate method works the same way it did before. This is one of the benefits of separating business logic from presentation logic.

Discussion

There are now three pieces to our user model. The first is the enhanced validation code. The user model now:

  • Provides getters and setters for the password attribute.
  • Makes sure that the hashed_password field in the database can be accessed from the outside.
  • Ensures that each user has a unique username.

When a new user is created, or when the password is changed, User ensures:

  • That the value of the password_confirmation attribute is equal to the value of the password attribute.
  • That the password is between 5 and 40 characters long.

The second section of code defines User class methods as before. We add one new class-level method, hashed, which performs the hashing function on a plaintext password. If we want to change hashing mechanisms in the future, we only have to change this method (and migrate any existing passwords).

The third piece of code in the model is a private instance method, update_password, which synchronizes the plaintext password attribute with the hashed version in the database. The call to before_save sets up this method to be called before a User object is saved to the database. This way you can change a users password by setting password to its plaintext value, instead of doing the hash yourself.

See Also

  • Recipe 13.14, "Validating Data with ActiveRecord"
  • Recipe 15.8, "Creating a Login System"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net