This is from the github page:
require 'bcrypt'
class User < ActiveRecord::Base
# users.password_hash in the database is a :string
include BCrypt
def password
#password ||= Password.new(password_hash)
end
def password=(new_password)
#password = Password.create(new_password)
self.password_hash = #password
end
end
It appears that to access the password method, you need to call it as an attribute from a create method:
#user.password = user_params[:password]
#user.save
Okay...fine? But where's the salt now stored? I just don't get it at all, how is this even remotely secure anymore?
To retrieve a hashed password, you need this method:
def password
#password ||= Password.new(password_hash)
end
And call it as an attribute:
if #user.password == params[:password]
give_token
else
false
end
So it appears everything's working without a salt...how does it do this?
This means I only need one column in my database to do with passowords now, right?password or password_hash instead of password_salt | password_hash?
Well then why does the github page say this:
But even this has weaknesses -- attackers can just run lists of
possible passwords through the same algorithm, store the results in a
big database, and then look up the passwords by their hash:
PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1"
Salts
And then this is what really gets me:
The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed:
Why are they explaining all of this if bcrypt is handling everything automatically?
hash(salt + p) #=> <really unique gibberish>
The salt is then stored along with the hash in the database, and used to check potentially valid passwords:
<really unique gibberish> =? hash(salt + just_entered_password)
bcrypt-ruby automatically handles the storage and generation of these salts for you.
Could someone explain how bcrypt stores and generates these salts? Why does it say it handles it all for me and then goes on to tell me how to generate a salt? Do I need to run something like this in my model: self.password_hash = hash(salt + p)
Argh so confused I used to get salts and hashes utterly and now they've changed it all. Terrible, unclear docs...they appear to show you how to use bcrypt without a salt with loads of examples, and then briefly mention how to do it properly with a salt down at the bottom.
Could someone please give me an example how to use the new version of bcrypt to generate a salt and hash, and how to authenticate?
Okay, the has_secure_password is really cool. You don't need to worry about salts and hashes anymore, the salt and hash are stored as one attribute ( password_digest) in the database.
It's saved in such a way that bcrypt knows which part of the password_digest string is the salt, and what is the hash.
If you're setting up authentication from scratch, you literally need to do the following:
1) Add the bcrypt rails gem:
gem bcrypt-rails
2) Add the has_secure_password method to the model tasked with handling your user records:
class User < ActiveRecord::Base
has_secure_password
end
3) Make sure your users table has a password_digest column:
class CreateUser < ActiveRecord::Migration
create_table :users do |t|
t.username
t.password_digest
end
end
4) Create a new method to create a a new empty user instance for the form to use:
class UsersController < ApplicationController
def new
#user = User.new
end
end
5) In the new view, make a form that creates populates the params hash' :password and :username entries:
<% form_for( #user ) do |f| %>
<%= f.text_field :username %>
<%= f.password_field :password %>
<% end %>
6) Back in our controller, permit the username and the password using strong params. The whole reason behind strong params is to prevent some cheeky chappy from using dev tools to create their own html form field (such as one pertaining to id) and populating the database with malicious data:
class UsersController < ApplicationController
def new
#user = User.new
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
7) Let's create the create method that will use these permitted entries to create a new user, populated by the form:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
#user.save
redirect_to root_path
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
Set up your routes as you see fit, and that's it! The user record's password_digest column will be automatically populated with one string comprised of a salt appended with the hash of the password. Very elegant.
All you need to remember: password -> password_digest.
In order to authorise the user and signout the user, create a sessions controller with a create method and a destroy method:
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to admin_root_path, :notice => "Welcome back, #{user.username}"
else
flash.now.alert = "Invalid email or password"
redirect_to root_path
end
end
def destroy
reset_session
flash[:info] = "Signed out successfully!"
redirect_to root_path
end
Hope this helps someone!
bcrypt got everything covered for you. Your password digest consists of a few types of information, bcrypt algorithm type, cost, salt and the checksum.
for example:
my_password = BCrypt::Password.create("my password")
#=> "$2a$10$.kyRS8M3OICtvjBpdDd1seUtlvPKO5CmYz1VM49JL7cJWZDaoYWT."
The first part: $2a$ is the variant of the algorithm see: Where 2x prefix are used in BCrypt?
The second part 10 is the cost parameter, you can increase it to slow down the process (logarithmic value) by providing a hash {cost: 12} as the second argument to create.
Now if you call my_password.salt you get "$2a$10$.kyRS8M3OICtvjBpdDd1se" which identifies the part that is being used as the key to creating your checksum.
And finally, your checksum is "UtlvPKO5CmYz1VM49JL7cJWZDaoYWT.". That's the reason if you call create the second time the string is going to be different as another salt will be used.
But as I mentioned earlier you don't need to do anything extra as all these are being taken care of for you.
Related
I'm building an app that has 3 models (Customer, Points, Admin). Customer has Points, Points belong to Customer. Then Admin has user_name and password_hash as attributes, storing the passwords via Bcrypt. Once a customer searches themselves via phone number, then their points show up. But to add points, an admin has to log in with just a password (code of 4 digits) then get access to adding points.
I'm having trouble how to find the admin via only a password, not user_name and password.
class AdminsController < ApplicationController
def new
#admin = Admin.new
end
def create
#Admin = Admin.new(admin_params)
if #admin.save
redirect_to root_path
else
flash[:error] = "incorrect data, please check form"
render new_admin_path
end
end
def login
#customer = Customer.find(params[:id])
# Need to get the input password
params[:password]
# Change the inputed password into a password hash
# inputed_password_hash (NEED HELP HERE)
# Compare the password hash with password hashes in the Admin model/database
# to see if it exists.
# if true, send to add points page
# if false, send back to customer page
if Admin.find_by(password_hash: inputed_password_hash)
redirect_to new_points_path
else
render customer_path
end
end
private
def admin_params
params.require(:admin).permit(:user_name, :password, :password_confirmation)
end
end
I've read several SO links on this topic. Even if you can hack it to get current_user in model, you shouldn't do it. So, what are my options in my case?
I'm using the devise_invitable gem, and one of the commands is User.invite!({:email => email}, current_user), which stores who the user is invited by (current_user). I'd like to have this information.
Currently, users are invited to join a private group, and this process is handled in my group.rb model:
# group.rb
def user_emails
end
def user_emails=(emails_string)
emails_string = emails_string.split(%r{,\s*})
emails_string.each do |email|
user = User.find_for_authentication(email: email)
if user
self.add user
GroupMailer.welcome_email(user)
else
User.invite!(email: email) # But I want this: User.invite!({:email => email}, current_user)
user = User.order('created_at ASC').last
self.add user
end
end
end
If relevant, it's just a text_area that receives these emails to process:
# groups/_form.html.erb
<%= f.text_area :user_emails, rows: 4, placeholder: 'Enter email addresses here, separated by comma', class: 'form-control' %>
Without having to re-arrange too much, how can I run User.invite!({:email => email}, current_user) in this process, so that this useful information (who is invited by whom) is stored in my database? Much thanks!
Update:
With #Mohamad's help below, I got it working.
# group.rb
def emails
end
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.group_invite user
else
add User.invite!({:email => email}, inviter)
end
end
end
# groups_controller.rb
def update
#group = Group.friendly.find(params[:id])
if #group.update_attributes(group_params)
emails = params[:group][:emails]
#group.invite_many(emails, current_user) # also put this in #create
redirect_to #group
else
flash[:error] = "Error saving group. Please try again."
render :edit
end
end
And then nothing in my User model because User.invite is defined already by devise_invitable and I didn't need to do anything else. This process is working great now!
There are some subtle issues with your code. There's a potential race condition on the else branch of your code where you try to add the last created user. I'm also unsure that you need a setter method here unless you are access emails from elsewhere in the instance of Group.
As suggested by others, pass the current user as an argument form the controller. I'm not sure how invite! is implemented, but assuming it returns a user, you can refactor your code considerably.
I would do somethng like this:
def invite_many(emails, inviter)
emails.split(%r{,\s*}).each do |email|
if user = User.find_for_authentication(email: email)
add user
GroupMailer.welcome_email user
else
add User.invite!(email, inviter)
end
end
end
# controller
#group.invite_many(emails, current_user)
# User.invite
def invite(email, inviter)
# create and return the user here, and what else is necessary
end
If you are calling user_emails() from the controller (and I'm guessing you are as that must be where you are receiving the form to pass in emails_string), you can pass in the current_user:
user_emails(emails_string, current_user)
and change user_emails to receive it:
def user_emails=(emails_string, current_user)
You can store the current_user with global scope ,like #current_user,which can be assigned in sessions controller,so in model you will just #current_user as the current user of the app.
In my Rails app I have an update action that users can use to update their profile.
The tricky thing I want to achieve is that if a user enters a new email address and saves it, that email address won't get saved to the email database field straightaway, but rather to a database field called new_email. The field email should remain untouched (at least until the user has confirmed that email address later on).
def update
current_email = #user.email
new_email = params[:user][:email].downcase.to_s
if #user.update_attributes(params[:user])
if new_email != current_email
#user.change_email(current_email, new_email)
flash[:success] = "Profile updated. Please confirm your new email by clicking on the link that we've sent you."
else
flash[:success] = "Profile updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
User model:
def change_email(old_email, new_email)
self.new_email = new_email.downcase
self.email = old_email
self.send_email_confirmation_link
end
The function above kind of works but is hard to test and doesn't feel right. Is there a smoother way to achieve this?
Thanks for any help.
If you change your form so that you're updating new_email, you can just put it all in a simple after_update hook.
after_update :check_new_email
private
def check_new_email
send_email_confirmation_link if new_email_changed?
end
I think you could use "virtual" attribute, called - let's say - email_input and show field for this attribute (instead of email) in view:
<%= f.text_field :email_input %>
Then in your model you should have:
class User < ActiveRecord::Base
attr_accessor :email_input
attr_accessible :email_input
before_save :set_email, :if => lambda{|p| p.email_input.present?}
# ...
def set_email
email_input.downcase!
if new_record?
self.email = email_input
else
self.new_email = email_input
send_email_confirmation_link
end
end
end
I'm writing an application for myself, so I've got no rush and the my only target is to do things properly.
For authentication I use devise, but I turned out customizing it a lot.
I've seen some good features coming in Rails 3.1 that could make easier to implement auth myself.
In general, when does Devise stops to be useful and starts getting in your way?
Here is a list of customization I have at the moment, beside views of course, but I still would like to implement at least SEO friendly urls.
# model/User.rb
#this method is called by devise to check for "active" state of the model
def active?
#remember to call the super
#then put our own check to determine "active" state using
#our own "is_active" column
super and self.is_active?
end
protected #====================================================================
# find in db the user with username or email login
def self.find_record(login)
where(attributes).where(["name = :value OR email = :value", { :value => login }]).first
end
# allow no case sensitive email
# (saved downcase, fetched downcase)
def self.find_for_authentication(conditions)
conditions[:email].downcase!
super(conditions)
end
# find the user in the db by username or email
def self.find_for_database_authentication(conditions)
login = conditions.delete(:login)
where(conditions).where(["name = :value OR email = :value", { :value => login }]).first
end
# Attempt to find a user by it's email. If a record is found, send new
# password instructions to it. If not user is found, returns a new user
# with an email not found error.
def self.send_reset_password_instructions(attributes={})
recoverable = find_recoverable_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
def self.find_recoverable_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
case_insensitive_keys.each { |k| attributes[k].try(:downcase!) }
attributes = attributes.slice(*required_attributes)
attributes.delete_if { |key, value| value.blank? }
if attributes.size == required_attributes.size
if attributes.has_key?(:login)
login = attributes.delete(:login)
record = find_record(login)
else
record = where(attributes).first
end
end
unless record
record = new
required_attributes.each do |key|
value = attributes[key]
record.send("#{key}=", value)
record.errors.add(key, value.present? ? error : :blank)
end
end
record
end
# password not required on edit
# see: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def password_required?
new_record?
end
# controllers/registrations_controller.rb
# devise controller for registration
class RegistrationsController < Devise::RegistrationsController
# update_attributes (with final S) without providing password
# overrides devise
# see: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
def update
# Devise use update_with_password instead of update_attributes.
# This is the only change we make.
if resource.update_attributes(params[resource_name])
set_flash_message :notice, :updated
# Line below required if using Devise >= 1.2.0
sign_in resource_name, resource, :bypass => true
redirect_to after_update_path_for(resource)
else
clean_up_passwords(resource)
render_with_scope :edit
end
end
end
Thank you
I'd just stick with devise for the time being, your changes aren't huge. However, I'd fork devise and extract the changes you've made into new features. Then attempt to get them pulled into devise itself. That way maintaining them doesn't fall on you, it can fall on the many.
Maintaining a full authentication system can be a real headache and ultimately its reinventing the wheel. It only takes one mistake can leave you wide open.
Also your new find_for_authentication method, this has now been supported in devise, put in your devise initializer...
config.case_insensitive_keys = [ :email ]
Good question - My view would probably be that as long as it makes things easier it's useful. You can always fork devise on github and put your customisation in there to some extent - which is what I've done on one project. I'm also a bit nervous about rolling my own authentication when it can be so important to get it right, especially if other people want to see stuff they shouldn't. But I'll be interested to see what others think.
I'm adding a password reset feature to my Rails application that uses Authlogic. I was following the guide here: http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic/ and everything works as I'd like except for one thing: the password reset form accepts blank passwords and simply doesn't change them.
I've been searching around, and have learned that this is the intended default behavior because it allows you to make user edit forms that only change the user's password if they enter a new one, and ignore it otherwise. But in this case, I specifically want to enforce validation of the password like when a user initially registers. I've found two possible solutions for this problem but haven't been able to figure out how to implement either of them.
1) Someone asked this same question on Google Groups:
User model saves with blank password
Ben's response was to use #user.validate_password = true to force validation of the password. I tried this but I get an undefined method error: undefined method 'validate_password_field=' for #<User>.
2) There seems to be an Authlogic configuration option called ignore_blank_passwords. It is documented here:
Module: Authlogic::ActsAsAuthentic::Password::Config#ignore_blank_passwords
This looks like it would work, but my understanding is that this is a global configuration option that you use in your initial acts_as_authentic call in the User model, and I don't want to change it application-wide, as I do have a regular edit form for users where I want blank passwords to be ignored by default.
Anyone found a solution to this? I see validate_password= in the change log for Authlogic 1.4.1 and nothing about it having been removed since then. Am I simply using it incorrectly? Is there a way to use ignore_blank_passwords on a per-request basis?
This is kind of an old thread, but since it is unanswered I'll post this.
I've managed to do it a bit more cleanly than the other solutions, "helping" authlogic validations with my own.
I added this to user:
class User < ActiveRecord::Base
...
attr_writer :password_required
validates_presence_of :password, :if => :password_required?
def password_required?
#password_required
end
...
end
You can reduce it to two lines by making an attr_accessor and using :if => :password_required (no interrogation), but I prefer this other syntax with the interrogation sign.
Then your controller action can be done like this:
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
#user.password_required = true
if #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
This will have a local effect; the rest of the application will not be affected (unless password_required is set to true in other places, that is).
I hope it helps.
This what I did.
class User < ActiveRecord::Base
attr_accessor :ignore_blank_passwords
# object level attribute overrides the config level
# attribute
def ignore_blank_passwords?
ignore_blank_passwords.nil? ? super : (ignore_blank_passwords == true)
end
end
Now in your controller, set the ignore_blank_passwords attribute to false.
user.ignore_blank_passwords = false
Here, you are working within the confines of AuthLogic. You don't have to change the validation logic.
User.ignore_blank_passwords = false
Use model, not object for setting this property.
def update_passwords
User.ignore_blank_passwords = false
if #user.update_attributes(params[:user])
...
end
User.ignore_blank_passwords = true
end
Maybe test the value of the parameter in the controller? (air code):
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
if #user.password.blank?
flash[:error] = "Password cannot be blank"
render :action => :edit
return
end
if #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
Apart from zetetic's solution you could do it this way:
def update
#user.password = params[:user][:password]
#user.password_confirmation = params[:user][: password_confirmation]
if #user.changed? && #user.save
flash[:notice] = "Password successfully updated"
redirect_to account_url
else
render :action => :edit
end
end
You're basically checking if authlogic changed the user record (which it doesn't if the password is empty). In the else block you can check if the password was blank and add an appropriate error message to the user record or display a flash message.