Devise authentication fails with 401 error - ruby-on-rails

I just switched from Sorcery to Devise. Sorcery is semi-inactive and although authentication is working in rails 5---test helpers are not. So, I decided to make the switch the Devise. I've been getting the dreaded 401 error:
Completed 401 Unauthorized
I have seen many questions regarding this...but, none of those answers helped me.
My question is a bit misleading because you will immediately assume I have some remnants of the Sorcery install. I might, but Ive thoroughly checked it all.
1 - If I use the forgot password functionality within Devise I will be auto logged in after changing my password---However, I can not log in using those sme credentials...get the 401 error.
2 - If I completely clear existing passwords (because I'm pretty sure sorcery encryption is diff than devise encryption) and update password using devise OR
3 - I have traced the path taken for authentication inside Devise and my problem surfaces when the following method is hit:
def valid_password?(password)
Devise::Encryptor.compare(self.class, encrypted_password, password)
end
It seems that encrypted_password != password even though the password was successfully updated using devise.
As part of following the auth path inside Devise I should also point out that I am using the Devise method user.skip_confirmation! which manually confirms the user---so, user confirmation should not be part of this issue.
I am using custom Devise views (although have not altered them yet). I have also generated a custom sessions controller because I think/thought this issue might be related to Pundit policy. However, I am skipping policy verification in the application controller using the following...so, Pundit should not be a problem:
after_action :verify_authorized, except: :index, unless: :devise_controller?
after_action :verify_policy_scoped, only: :index, unless: :devise_controller?
cookies_serializer has been brought up a number of times in other answers...I have tried :json, :marshal, and :hybrid with no affect.
So, authentication is failing because Devise::Encryptor.compare(self.class, encrypted_password, password) returns false. But, if Devise saved the new password---how can compare not return true. And, yes---I have entering the credentials correctly.
Thanks for the help!

Ok. So, attr_accessor :password, :password_confirmation is no longer needed in Devise---and, was responsible my problem. By setting attr_accessor :password my app was creating a password=() method, which was overriding the same method in devise. The result was no matter what my password ended up being ''. I originally found this by creating my own password setter:
def password=(new_password)
#password = new_password
self.encrypted_password = password_digest(#password) if #password.present?
end
Which is the same exact method in Devise internals. With this method my password saved and I was able to log in. I thought this was strange...so, I removed my custom method, and the attr_accessor and authentication continued to work. So, basically the attr_accessor overrode the devise method, then I created a setter method that overrode the attr_accessor method.
It was a lot of work and in the end was something so simple. I hope this helps someone else. I have some additional testing to do and if I find anything else I will amend this.

Related

Using Devise/DatabaseAuthenticatable module to check password/salt a string

I'm building an app that uses Devise to manage user state. I'm also building an API in that same app that receives a username and password from a POST request
What I'm trying to accomplish is:
Get the user by username from the database (done, straightforward)
Use Devise::Models::DatabaseAuthenticatable to take the password the user passed in, encrypt it, compare it against the encrypted_password field on the User model and if they're the same, proceed with the rest of my code
The second bullet above is what I'm having trouble with. In a console, I can't seem to get an instance of the module Devise::Models::DatabaseAuthenticatable to try the various instance methods that you can find here in the docs.
Any help would be greatly appreciated!
If I understood your question correctly, you can use .valid_password? devise method. Something like that:
#assuming you'll receive nested params like user[email], user[password]...
user_params = params.require(:user).permit(:email, :password)
user = User.find_by(email: user_params[:email])
return head 403 if user.nil?
valid = user.valid_password?(user_params[:password]) #true or false...
return head 403 unless valid
sign_in(user) #devise helper: if you want to sign in that user
You can also check another approachs, like devise token auth gem.

How to md5 a password before registering the user in devise?

In my Rails app, I use devise to manage users with devise's default configuration. I need to md5 the user-provided password before going down into the devise layer. That is, two steps are included:
(1) (2)
password_in_plain --- password_in_md5 --- password_in_bcrypt.
The first one(1) is our concern, the last one(2) is not (devise takes care of it).
I generated two devise controllers: registrations and sessions, and added a before_filter to do the job -- md5 the plain password user provided. The user can be registered with a success, but login always fail. Here is my code:
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :md5_password_params, only: [:create]
protected
def md5_password_params
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
params[:user][:password_confirmation] = Digest::MD5.hexdigest(params[:user][:password_confirmation])
end
end
class Users::SessionsController < Devise::SessionsController
before_filter :md5_password, only: [:create]
protected
def md5_password
params[:user][:password] = Digest::MD5.hexdigest(params[:user][:password])
end
end
What's wrong here? Please help. Thanks in advance.
Background: one of my other server apps (written in c++) will create user records in the same database directly. For safety reason, a md5 password will be sent to this server when users make registrations in their phones.
Instead of doing this on the controller level and mucking about with the params you can create your own encryptor for Devise.
One major reason you would want to do this is that if your user fills in the registration form and submits it with invalid values both the password and confirmation fields will contain a MD5 digest of what the user originally typed. This is because you are mutating the params hash before it is fed to the model.
Submitting the form again means that the password is encrypted twice and no longer matches what the user thinks the original password is. On the plus side its extremely secure since nobody can actually guess their password ;)
Based on the current version of Devise you should be able to do something like the following to ensure the password is encrypted before a digest is created and before trying to compare plaintext with the encrypted password.
Make sure to add the devise-encryptable gem to your Gemfile. Its required for anything else than default encryption.
# config/initializers/md5.rb
require 'digest/md5'
module Devise
module Encryptable
module Encryptors
class BCryptPlusMD5 < Base
def self.digest(klass, password)
super(klass, Digest::MD5.hexdigest(password))
end
def self.compare(klass, hashed_password, password)
super(klass, hashed_password, Digest::MD5.hexdigest(password))
end
end
end
end
end
You would can configure what encryptor to use in config/initializers/devise.rb - however I have not been able to find a good source on how the class lookup works.
https://github.com/plataformatec/devise/blob/master/lib/devise/encryptor.rb

Optional password during authentication with Devise

If I've got a rails application and I'd like to add authentication to with Devise, how would I allow users who have a null password in the database to sign in without one?
I'm interested in hearing answers along the lines of the lifecycle and what files I'd have to author to get it done.
Step 1: Allow the record to be saved.
Step 2: Sign in the record
To allow the record to be saved, you'll want to do validations yourself. I describe here how to do custom validations: http://jessewolgamott.com/blog/2011/12/08/the-one-where-devise-validations-are-customized/ .... In your case, you'll want to remove the password validations.
To sign in the record, you'll need to have a custom sign in path. You can override the devise sessions controller, but this could do the trick:
class SessionsController < ApplicationController
def create
user = User.find_by_email!(params[:session][:email])
sign_in user
redirect_to root_path
end
end
It turns out, Devise is built on Warden. This means that I only have to create my own custom Warden strategy:
https://github.com/hassox/warden/wiki/Strategies

How to authenticate Token with devise

I want to use devise' token_authenticatable helper to authenticate users against the system.
I found some older documentations where a method named valid_authentication_token?(...) is used but couldn't find the same in newer devise version.
So what's the right way to authenticate a user?
Should I request the Model for user with named token and checking if email-adresses match?
Thanks a lot for your help.
PascalTurbo
If you add
t.token_authenticatable
to you user ActionRecord, and add
devise :token_authenticatable
to your User model
and specify which param is your token key in config/initializer/devise, something like this:
config.token_authentication_key = :auth_token
then controllers that use
before_filter :authenticate_user! # Tell devise to use :user map
to authenticate. after the authenticate_user!, individual methods can test using
user_signed_in?
will authorize users either by the login session or the devise authorization token that is passed on the query string or passed using HTTP basic authentication. See Devise helper code for details.

authlogic UserSession.create(#user) giving unauthorized_record

I am trying to create a session explicitly like this UserSession.create(#user, true) but the session is not getting created, current_user is nil.
But when I do this, I get < #UserSession: {:unauthorized_record=>""}>us = UserSession.create(#user, true)
RAILS_DEFAULT_LOGGER.info(us.inspect) #=> UserSession: {:unauthorized_record=>""}
I had a look at Authlogic::Session::UnauthorizedRecord here it says
Be careful with this, because Authlogic is assuming that you have already confirmed that the user is who he says he is. For example, this is the method used to persist the session internally. Authlogic finds the user with the persistence token. At this point we know the user is who he says he is, so Authlogic just creates a session with the record. This is particularly useful for 3rd party authentication methods, such as OpenID. Let that method verify the identity, once it’s verified, pass the object and create a session.
which is exactly what I am trying to do (i am authenticating using omniauth and creating session using authlogic).
How do I fix this, so that I can get a valid session in current_user ?
I had a similar issue caused by the persistence_token being nil on the user. Reset it before creating the UserSession. So...
#user.reset_persistence_token!
UserSession.create(#user, true)
I'm not sure about the .create(object, bool) method signature, but the following works using authlogic.
class Api::ApiBaseController < ApplicationController
protected
def verify_token
return false if params[:token].blank?
#session = UserSession.new(User.find_by_single_access_token(params[:token]))
#session.save
end
end
If that doesn't work for you -- I think the #user isn't being set correctly.
If you map the active_record_store to the authlogic user_sessions table your session information will be stored in the database, and you will be able to store larger sets of data.
Inside your config folder:
config/initializers/session_store.rb
Comment out App::Application.config.session_store :cookie_store, :key => '_App_session'
Add or uncomment App::Application.config.session_store :active_record_store
Inside of config/application.rb
At the end of the class for you application add:
ActiveRecord::SessionStore::Session.table_name = 'user_sessions'
Restart your app, and any information stored in the user session will be saved in the authlogic user_sessions table.
Goto: http://apidock.com/rails/ActiveRecord/SessionStore
For more information
For now you can replace
UserSession.create #user
to
UserSession.create :email => #user.email, :password => #user.password
not a big deal.
But that caught me other way. I forgot that my user got active? == false when created. I've set it to true and session is created.
I ran into this problem today. In my case it ended up being related to CSRF tokens.
We are creating a user and session in our app in response to an OAuth callback. It appears that if the CSRF token is invalid, which would be the case when coming from a third party, authlogic won't create the user session.
Can't verify CSRF token authenticity
The fix was simple:
class Oauth::UserSessionsController < ApplicationController
skip_before_action :verify_authenticity_token, only: :callback
def new
# code removed...
end
def callback
# code removed...
UserSession.create(#user)
redirect_to root_path
end
end

Resources