Creating secure login code in Ruby on Rails
Many web sites require secure user authentication, but a shocking number of them implement it very badly. At the time of posting one of the best ways of dealing with this problem not to store the password in the database, but instead to save a “salt” and a “hash”. The salt is a set of 6 random characters which when combined with the users password and encoded, is equal to the hash. Before going any further, you should be aware that there are plugins for rails which deal with salted logins (http://wiki.rubyonrails.org/rails/pages/SaltedHashLoginGenerator), but I believe it is best to be able to write the code yourself, or at least to have a basic understanding.
user migration
t.column :username, :string t.column :password_salt, :string t.column :password_hash, :string
app/models/user.rb:
require 'digest/sha2'
class User < ActiveRecord::Base
attr_accessor :password
def password=(pass)
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp
self.password_salt, self.password_hash = salt, Digest::SHA256.hexdigest(pass + salt)
end
def self.authenticate(username, password)
user = User.find(:first, :conditions => [”username = ?”, username])
if user.blank? || Digest::SHA256.hexdigest(password + user.password_salt) != user.password_hash
raise “Username or password invalid”
end
user
end
end
application_controller.rb
def check_authentication
if session[:user].blank?
session[:intended_controller] = controller_name
session[:intended_action] = action_name
redirect_to :controller => 'user', :action => 'login'
end
end
app/controllers/user_controller.rb:
def login
if request.post?
begin
session[:user] = User.authenticate(params[:username],params[:password]).id
redirect_to :controller => session[:intended_controller], :action => session[:intended_action]
rescue
flash[:notice] = "Username or password invalid"
end
end
end
def logout
session[:user] = nil
redirect_to :controller => :user, :action => :index
end
def register
if request.post?
@user = User.new(params[:user])
if @user.save
redirect_to :action => :account_creation_success, :id => @user
end
end
end
app/views/user/login.rhtml:
<%= flash[:notice] -%> <% form_tag :controller => :user, :action => :login do %> <fieldset><label for=”user_username”>Username</label> <%= text_field 'user', 'username' -%><label for=”user_password”>Password</label> <%= password_field 'user', 'password' -%></fieldset> <%= submit_tag 'Login' -%> <% end %>
app/views/user/register.rhtml:
<% form_tag :controller => :user, :action => :register do %> <fieldset><label for=”username”>Username>/label> <%= text_field_tag 'user', nil, 'username' -%><label for=”password”>Password</label> <%= password_field_tag 'user', nil, 'password' -%></fieldset> <%= submit_tag 'Register' -%> <% end %>
Now use a before_filter on any controllers where you need to login.
Trackbacks
Use this link to trackback from your own site.
Good to see you’re working hard, Matt, even on a Friday. You’re really worth your salt. Arf!
So what’s the difference between copy/pasting all this provided code, and getting the same achieved with running:
ruby script/generate salted_login
Sure, you have a better understanding of how things work (and the login generator used to have a bug, I don’t remember if it was fixed since last year). But from reader’s perspective, both are just sets of code.
Tony, you are absolutely right. I actually published this post prematurely, with the intention of going back to it within 24 hours to include some explanation of how it works, and what it does. Please accept my apologies, I promise to make the changes very soon!
Ah, that’s.. an odd practice for blogging, but alright. Send me a ping when you update the article, I’ll check it out ;)
You might be interested in this post:
http://www.rorsecurity.info/2007/03/20/logingenerator-and-loginsugar-security-vulnerabilities/
Nice to see u that code, But i think u have to also present that example here and give chanse to peple that they can execute that code.. from that u can create more secure than this …..
by the way thats great
Hardy
This is just the authentication recipe from Rails Recipes[1] nearly verbatim. At least give credit where credit’s due.
[1] http://www.pragmaticprogrammer.com/titles/fr_rr/
sjs - I have not read Rails Recipes so I can assure you that this is not from it. However, I did put this code together a while ago from reading around on the net. If it is someone else’s work then I apologise. My intention is only to help others with their coding problems and promote rails.
Perhaps that is the idiomatic way to generate salt in Ruby, but that was what led me to think you got it from the recipe. Sorry about that!
Hello! Good Site! Thanks you! pvhsrnfkhmg
Nice tutorial, but what’s the database schema behind this code?
I have added the database schema in quickly this morning, as well as updating some of the code slightly.
Thanks for the tutorial… it helped me a lot!
Regards
I’m glad I helped you Robert.
Thanks very much for the workingwithrails recommendation!
hello,
i got the following error when i tried the above code to implwment when trying to register.
SyntaxError in UserController#register
app/models/user.rb:12: Invalid char `\224′ in expression
app/models/user.rb:14: Invalid char `\223′ in expression
app/models/user.rb:14: Invalid char `\224′ in expression
RAILS_ROOT: ./script/../config/..
Application Trace | Framework Trace | Full Trace
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:202:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:94:in `require_or_load’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:248:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:452:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:464:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:260:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:468:in `const_missing’
#{RAILS_ROOT}/app/controllers/user_controller.rb:20:in `register’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:202:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:94:in `require_or_load’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:248:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:452:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:464:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:260:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:468:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `send’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `perform_action_without_filters’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:632:in `call_filter’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:619:in `perform_action_without_benchmark’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue’
c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/rescue.rb:83:in `perform_action’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `send’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `process_without_filters’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:624:in `process_without_session_management_support’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session_management.rb:114:in `process’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:330:in `process’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/dispatcher.rb:41:in `dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:113:in `handle_dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:79:in `service’
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service’
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run’
c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread’
c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread’
c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `each’
c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:63:in `dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/servers/webrick.rb:59
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/server.rb:39
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
script/server:3
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:203:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:202:in `load_file’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:94:in `require_or_load’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:248:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:452:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:464:in `const_missing’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:260:in `load_missing_constant’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:468:in `const_missing’
#{RAILS_ROOT}/app/controllers/user_controller.rb:20:in `register’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `send’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `perform_action_without_filters’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:632:in `call_filter’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:619:in `perform_action_without_benchmark’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue’
c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/rescue.rb:83:in `perform_action’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `send’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `process_without_filters’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:624:in `process_without_session_management_support’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session_management.rb:114:in `process’
c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:330:in `process’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/dispatcher.rb:41:in `dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:113:in `handle_dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:79:in `service’
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service’
c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run’
c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread’
c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread’
c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `each’
c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start’
c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/webrick_server.rb:63:in `dispatch’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/servers/webrick.rb:59
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in’
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require’
c:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/server.rb:39
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’
c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
script/server:3
Request
Parameters: {”user”=>{”username”=>”ganesh”, “password”=>”kumar”}, “commit”=>”Register”}
Show session dump
—
flash: !map:ActionController::Flash::FlashHash {}
Response
Headers: {”cookie”=>[], “Cache-Control”=>”no-cache”}
HELP ME TO SOLVE THE ABOVE ERROR.
Thanks And Regards
Ayyappan K.
Does this prevent the password from being sent naked from the browswer to webserver? I am more concerned about that, than someone hacking my database? I suppose I should use JS to hash it client side?
I am just new to Rails, please did you define action “account_creation_success”. I couldn’t figure it out