Search code examples
ruby-on-railsauthenticationnullnomethoderror

rails "NoMethodError in SessionsController#create ". Apparently a nil object I don't expect, or so says the error


I am trying to sign in users with sessions. My controller has a method 'create' sessions:

def create
    user = User.authenticate(params[:session][:email], params[:session][:password])

    if user.nil?
        #create an error message and re-render the signin form
        flash.now[:error] = "Invalid email/password combination"
        @title = "Sign in"
        render 'new'
    else
        #sign in user and redirect to the user's show page
        sign_in user
        redirect_to user
    end
end

the class method 'authenticate' always returns null. I uses the console to create users where 'nil?' returns false, and then I call 'authenticate' and it returns nil, everytime. I don't know why and have pored over it for hours. At first I thought it was the way I was passing the parameters to the authenticate method, but those should be right, and since the auth method doesn't even work in console with regular values it probably isnt the parameters but the structure of 'authenticate'. Here is the 'authenticate' method and its helper methods:

#defining a class method for "User" to authenticate a user who submits an email and password
def User.authenticate(email, submitted_password)
 user = find_by_email(email)
 return nil  if user.nil?
 return user if user.has_password?(submitted_password)
end 

# Return true if the user's password matches the submitted password.
def has_password?(submitted_password)
    encrypted_password == encrypt(submitted_password)
end

def self.authenticate_with_salt(id, cookie_salt)
    user =find_by_id(id)
    (user && user.salt == cookie_salt) ? user : nil
end

private
    def encrypt_password
        self.salt = make_salt if new_record?
        self.encrypted_password = encrypt(password)
    end

    def encrypt(string)
        secure_hash("#{salt}--#{string}")
    end

    def make_salt
        secure_hash("{#Time.now.utc}--#{password}")
    end

    def secure_hash(string)
        Digest::SHA2.hexdigest(string)
    end

Anyone know what the deal is? and I am happy to supply anymore information. I searched around for potential solutions, and couldn't find any.

sign_in(user) method and helper methods stuff:

module SessionsHelper

def sign_in(user)
    #set cookie permanently
    cookies.permanent.signed[:remember_token] = [user.id, user.salt]
    #define "current_user" so it can be used all over the place
    self.current_user = user
end

def current_user=(user)
    @current_user = user
end

def current_user
    @current_user ||= user_from_remember_token
end

def signed_in?
    !current_user.nil?
end

def sign_out
    cookies.delete(:remember_token)
    @current_user = nil
end

private

    def user_from_remember_token
        User.authenticate_with_salt(*remember_token)
    end

    def remember_token
        cookies.signed[:remember_token] || [nil, nil]
    end
end

users_controller:

class UsersController < ApplicationController
# GET requests are automatically handled by the "show" action.
def show
    @user = User.find(params[:id])
    @title = @user.name
end

def new
    @user = User.new
    @title = "Sign up"
end

def create
    @user = User.new(params[:user])
    if @user.save
        sign_in @user
        flash[:success] = "Welcome to the Sample App!"
        redirect_to @user
    else
        @title = "Sign up"
        render 'new'
    end
end
end

Solution

  • I see at least two problems.

    1) Don't look for the :email and :password in the :session. They should be in the :params hash, not in the :session hash inside :params. Since you aren't getting the correct :email, you never find the User record, so User.authenticate always returns nil.

    2) User.authenticate will return nil if the email isn't found. If the email is found, it returns the value of submitted_password == encrypted_password. You have three possible return values (nil, true, false), but only two control paths, one for nil (record not found) and one for all other cases, so you log the user in regardless of the password test result.