I want to make my own simple authentication.
So I made User model and Users controller. And I have a form for sign up
Part of my Users controller.
def create
#user = User.new(user_params)
if #user.save
redirect_to root_path, notice: "You've successfully singed up."
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation)
end
And error:
unknown attribute: password
In my Users table I have username and password_digest for each User. So there're not column like password.
I've just watched Rails Cast episode but it is about Rails 3 and author uses attr_accessible but I've read in Rails 4 we should use strong parameters.
How I can deal with it?
It is likely that you are using has_secure_password and you have not enabled it correctly. has_secure_password adds two virtual attributes to your model: password and password_confirmation. The error unknown attribute could mean that those two attributes are not being set correctly on your model.
You should under no circumstances pass the password_digest as a param since BCrypt and has_secure_password use this column to compute a hashed version of your password attribute. In other words it has no business being in the form.
Make sure you have:
Included the BCrypt gem in your gemfile
Correct included the has_secure_password module in your user model.
Your use of strong_parameters is correct. The problem is in your model, likely to do with has_secure_password, and not strong_paramters. So this line is correct:
def user_params
params.require(:user).permit(:username, :password, :password_confirmation)
end
Strong parameters filter params to avoid mass assignments until they have been whitelisted.
The permit method identifies the list of allowed parameter and must correspond to your model's attributes.
So, in your case you should write
def user_params
params.require(:user).permit(:username, :password_digest)
end
Related
this is my first post on stack. I am just curious as I have read regarding password hashing and more often I was told to use BCrypt instead. However, are there any documentations that I can use in order for me to try and hash the passwords on my own? I know using BCrypt is optimal for safety reasons but I would like to play around and understand the codes or functions surrounding hashing a password when the user signs up without using BCrypt or any other gems.
For this, I have tried using 'SecureRandom' and 'Digest' but I still seem to be unable to get the desired output. Perhaps I am still new at this.
This is my codes. I am very new at this.
require 'digest'
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url
else
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, password_hash([:password]))
end
def password_hash(password)
hash = Digest::SHA256.hexdigest(password)
end
end
Once I convert it into strings, the password column in the database will be empty after the user sign up. I know my codes are a messy but I would like to try to encrypt the passwords without using gems.
My team always use Devise gem for work with users. It simple for using and have
great functional as: password encrypt, send email, authorization and other
I'm having trouble implementing strong parameters, receiving the Undefined Method attr_accessible error locally.
Could anyone explain what exactly I have done wrong here.
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end
And in user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_secure_password
validates_presence_of :password, :on => :create
end
And perhaps a foolproof fix for this...I've tried a number of attempts but I just can't seem to get this right.
strong_params are usually done in the controller, not in the model. it's also described like this in the api. so, there's also no need for you to set attr_accesible. this way different controllers can also set different fields on a model, e.g. a backend users controller could be allowed to set an admin flag, while the users controller on the frontend is not allowed to do that.
so, your user_params method belongs in your UsersController, and the create and update action use user_params to filter out the params you don't allow to be set. e.g.:
#user = User.new(user_params)
Rails 4 uses strong params by default, and you don't need attr_accessible. Also in rails 4 you permit params in the controller instead of the model.
How is attr_accessible used in Rails 4?
In my devise sign-up page, I have implemented an IP tracking feature that, when a user signs up, sends the country the user comes from, in order to populate the newly created account's attribute user_country.
I'd like to know if it's possible to implement a sort of validation on -user_country the same way I do for other User attributes (email, password...) when the user is created, to implement a sort of validation on user_country to be sure it's not empty nor nil, i.e that a user must always have a country identified.
I can't use the classic way with validates :user_country, presence: true, because when the user is created the user_country is still not filled but WAITS that RegistrationController makes a call to geocoder this way => see the method on the bottom called 'after_sign_up_path_for'
/app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
layout 'lightbox'
def update
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
# required for settings form to submit when password is left blank
if account_update_params[:password].blank?
account_update_params.delete("password")
account_update_params.delete("password_confirmation")
end
#user = User.find(current_user.id)
if #user.update(account_update_params) # Rails 4 .update introduced with same effect as .update_attributes
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
# for Rails 4 Strong Parameters
def resource_params
params.require(:user).permit(:email, :password, :password_confirmation, :current_password, :user_country)
end
private :resource_params
protected
def after_sign_up_path_for(resource)
resource.update(user_country: set_location_by_ip_lookup.country) #use concerns/CountrySetter loaded by ApplicationController
root_path
end
end
Is there a way to implement a sort of 'validate' in this kind of situation?
My understanding is you want a User model to validate user_country field but only during steps after the initial creation?
class User
validates_presence_of :user_country, on: :update
end
will only validate on update and not creation. See http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_presence_of for other options that you can add.
But for your use case, it seems odd that you need to do this in the controller after signup. I think it makes more sense to always validate user_country then inject the attribute during User creation in the controller.
The prime example of this feature is when creating a user. Instead of saving 'password' to the database you want to take an instance's password and create a password_hash and password_salt from it.
So if I'm creating a form where a user can be created, how can I have a password field if there's no 'password' attribute?
I think previously this could be solved by using attr_accessor in the user model, but I don't know how to do this with strong params:
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
Just results in a UnknownAttributes error when trying to create a new instance via the user_params:
#user = User.new(user_params)
If you want to add to your strong params, you can use the .merge method
private
def user_params
params.require(:user).permit(:x, :y, :z).merge(hair_color: "brown")
end
Alternatively, if you're looking to manipulate the data on save, you'd probably use the ActiveRecord callbacks, namely before_create:
#app/models/user.rb
Class User < ActiveRecord::Base
before_create :hash_password
private
def hash_password
if password #-> == self.password
#hash the password
#runs after validations
#runs just before DB insert
end
end
end
With the recent upgrade to Rails 4, updating attributes using code resembling the below does not work, I get a ActiveModel::ForbiddenAttributes error:
#user.update_attributes(params[:user], :as => :admin)
Where User has the following attr_accessible line in the model:
attr_accessible :role_ids, :as =>admin
# or any attribute other than :role_ids contained within :user
How do you accomplish the same task in Rails 4?
Rails 4 now has features from the strong_parameters gem built in by default.
One no longer has to make calls :as => :admin, nor do you need the attr_accessible :user_attribute, :as => admin in your model. The reason for this is that, by default, rails apps now have 'security' for every attribute on models. You have to permit the attribute you want to access / modify.
All you need to do now is call permit during update_attributes:
#user.update_attributes(params[:user], permit[:user_attribute])
or, to be more precise:
#user.update_attributes(params[:user].permit(:role_ids))
This single line, however, allows any user to modify the permitted role. You have to remember to only allow access to this action by an administrator or any other desired role through another filter such as the following:
authorize! :update, #user, :message => 'Not authorized as an administrator.'
. . . which would work if you're using Devise and CanCan for authentication and authorization.
If you create a new Rails 4 site you'll notice that generated controllers now include a private method which you use to receive your sanitised params. This is a nice idiom, and looks something like this:
private
def user_params
params.require(:user).permit(:username, :email, :password)
end
The old way of allowing mass assignment was to use something like:
attr_accessible :username, :email, :password
on your model to mark certain parameters as accessible.
Upgrading
To upgrade you have several options. Your best solution would be to refactor your controllers with a params method. This might be more work than you have time for right now though.
Protected_attributes gem
The alternative would be to use the protected_attributes gem which reinstates the attr_accessible method. This makes for a slightly smoother upgrade path with one major caveat.
Major Caveat
In Rails 3 any model without an attr_accessible call allowed all attributes though.
In Rails 4 with the protected_attributes gem this behaviour is reversed. Any model without an attr_accessible call has all attributes restricted. You must now declare attr_accessible on all your models. This means, if you haven't been using attr_accessible, you'll need to add this to all your models, which may be as much work as just creating a params method.
https://github.com/rails/protected_attributes
This problem might also be caused by the Cancan gem
Just add to application_controller.rb
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
Works without any further modifications of code
got it from here: https://github.com/ryanb/cancan/issues/835#issuecomment-18663815
Don't forget to add your new user_params method to the controller action:
def create
#user = User.new(user_params)
#user.save
redirect_to 'wherever'
end
def create
#user = User.create(user_params)
....
end
def update
#user = User.find(params[:id])
if #user.update_attributes(blog_params)
redirect_to home_path, notice: "Your profile has been successfully updated."
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :age, :others)
end