In the devise source code listed here:
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb
What exactly does line 56 do? In other words, I am not sure how devise determines whether a user is signed in.
It looks like it calls warden.authenticate with the scope of user(provided that user is what the model is)
Would I have to dive into the Warden codebase as well?
Devise delegates the work to warden. Warden checks if the username and password are valid.
Warden::Strategies.add(:my_strategy) do
def valid?
params[:username] && params[:password]
end
def authenticate!
u = User.find_by_username_and_password(
params[:username],
params[:password] # you should encrypt this. ;)
)
u.nil? ? fail!("Couldn't log in") : success!(u)
end
end
Related
I'd like to build a web app based on ruby on rails. For authentication I am using the devise gem. Everything is good : I can create accounts, login, logout etc.
But here I have an issue. I'd like to be able to sign up without providing password but still have the ability to register with password for another account.
I've set the password lenght from 0 to 128 on config/initializers/devise.rb
config.password_length = 0..128
But what are the next steps to do what I want?
Thank you
Ok, I reply here as answer.
Thanks to Ammar Shah i figured out how to get users with password and users without password.
First of all create a file in lib/devise/strategies (create the folders) named database_authenticatable.rb with following code :
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class DatabaseAuthenticatable < Authenticatable
def authenticate!
if password.blank?
authentication_hash[:encrypted_password] = ''
end
resource = mapping.to.find_for_database_authentication(authentication_hash)
hashed = false
if validate(resource){ hashed = true; resource.valid_password?(password) }
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
mapping.to.new.password = password if !hashed && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end
end
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
Then in the devise_create_user.rb migration add :
t.string :remember_token
Finally in user.rb model :
before_create :remember_value
def valid_password?(password)
if password.blank?
true
else
super
end
end
def password_required?
new_record? ? false : super
end
def remember_value
self.remember_token ||= Devise.friendly_token
end
Thank you Ammar Shah for helping me !
Reset password length in config/initializers/devise.rb to the default value and use this answer to make password optional.
Also, here is a complete implementation of gradual engagement feature in devise wiki. It depends what exactly you want you achieve.
I am looking to set a secondary password by which I can authenticate a user for a login as from admin. The reason for this work around is the front end is a single page application.
Each user has been given a unique login_as string. now I need to configure Devise to compare the login_as if the password fails.
Any help is appreciated. I am of course open to an alternative solution if there is a better way.
Thanks.
This post from Duncan Robertson was very helpful in solving my issue. I essentially created an override strategy and called it in the devise.rb file. I had some concern regarding tampering with a large user base but it has proved successful. By adding a column to users named ":signin_as" and then setting it to a default unique string with a rake I then had what I needed to fallback on if the initial sign in failed.
the override strategy (config/initializers/auth_override.rb)
module Devise
module Strategies
class AuthOverride < Authenticatable
def custom_auth(user, signin_as)
if user[:signin_as] == signin_as
return true
else
return false
end
end
def authenticate!
user = User.find_by_email(email)
if user
if user.valid_password?(params[:password])
success!(user)
elsif custom_auth(user, params[:password])
success!(user)
else
fail
end
else
fail
end
end
end
end
end
including the strategy in devise (config/initializers/devise.rb)
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :auth_override
end
Does devise have call backs when a user signs in and out?
This is what I came up with:
Warden::Manager.after_authentication do |user,auth,opts|
user.update_attribute(:currently_signed_in, true)
end
Warden::Manager.before_logout do |user,auth,opts|
user.update_attribute(:currently_signed_in, false)
end
This is what I came with to track users that are currently signed in.
I'm no expert but I believe the callbacks (hooks) are at the Warden level (Devise is built on top of Warden).
after_set_user and before_logout in Warden should do the trick for you but there are other options listed in Warden::Hooks
You can overwrite sign_in in your application controller like this
def sign_in(*args)
super(*args)
# do whatever you want here
token = current_user.authentications.where(provider: "facebook").first.token
facebook = Koala::Facebook::API.new(token)
session[:facebook] = facebook
end
I'm having a problem matching user password using devise gem in rails. User password stored on my db which is encrypted_password and i am trying to find user by password, but I don't understand how to match password from form and encrypted_password in my db.
User.find_by_email_and_password(params[:user][:email], params[:user][:password])
I think this is a better, and more elegant way of doing it:
user = User.find_by_email(params[:user][:email])
user.valid_password?(params[:user][:password])
The other method where you generate the digest from the user instance was giving me protected method errors.
Use Devise Methods
Devise provides you with built-in methods to verify a user's password:
user = User.find_for_authentication(email: params[:user][:email])
user.valid_password?(params[:user][:password])
For Rails 4+ with Strong Params, you can do something like this:
def login
user = User.find_for_authentication(email: login_params[:email])
if user.valid_password?(login_params[:password])
user.remember_me = login_params[:remember_me]
sign_in_and_redirect(user, event: :authentication)
end
end
private
def login_params
params.require(:user).permit(:email, :password, :remember_me)
end
I think the better one will be this
valid_password = User.find_by_email(params[:user][:email]).valid_password?(params[:user][:password])
I would suggest this.
user = User.where("email=? OR username=?", email_or_username, email_or_username).first.valid_password?(user_password)
For 2022, devise is required to add :database_authenticatable to use valid_password? method
class User < ApplicationRecord
devise :xxxable, :yyyable, :database_authenticatable
But, if you need only to verify the entering password just go like this
class User < ApplicationRecord
devise :xxxable, :yyyable#, :database_authenticatable
def valid_password?(verifying_word)
password_digest_instance = BCrypt::Password.new(self.password_digest)
current_password_salt = password_digest_instance.salt
hashed_verifying_word_with_same_salt = BCrypt::Engine.hash_secret(verifying_word, current_password_salt)
Devise.secure_compare(hashed_verifying_word_with_same_salt, self.password_digest)
Then
user = User.find_by(email: params[:user][:email])
user = nil unless user.try(:valid_password?, params[:user][:password])
I'm using Devise for authentication in my rails app and I'd like to be able to block certain accounts and prevent users from reregistering with a blocked email. I'm just not sure what the best way is to go about it.
My first thought was to override the sessions and registrations controllers to check the model for a user with a blocked bit, but I have a feeling there might be a more elegant way.
The best approach is to do it in Devise way:
Below assumes that you are using Devise database_authenticatable module and your application's users model names User.
1. Implement an account_active? method.
Add boolean account_active column in users table or define account_active? method in User model (you can chose your own method name). For example:
# app/models/user.rb
def account_active?
blocked_at.nil?
end
2. Overwrite the active_for_authentication? method in your model (User).
# app/models/user.rb
def active_for_authentication?
super && account_active?
end
3. Add method which returns translation for flash message.
Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using the inactive_message method.
# app/models/user.rb
def inactive_message
account_active? ? super : :locked
end
And that's it. You don't need to care about sign_out or redirect_to user.
Moreover, user is locked immediately, with next request, not after next sign in.
More: devise/authenticatable.rb.
I would do it like this:
def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.banned?
sign_out resource
banned_user_path
else
super
end
end
A better solution is to override the active_for_authentication? method on the devise model (User). Like so:
def active_for_authentication?
super && !self.banned?
end