Log Out won't work correctly

Hey all,

I had a fairly simple setup for authentication that was working but wanted to add a remember me radio button. Since adding the radion button, adding a column called Auth_Token to my DB and using it to store cookies I can’t seem to get my log out button to actually log the user out.

I keep receiving the following message:

ArgumentError in SessionsController#destroy

wrong number of arguments (0 for 1)

My setup is as follows:

Sessions Controller

class SessionsController < ApplicationController
	def new
	end
	
	def create
		user = User.find_by_email(params[:email])
		if user && user.authenticate(params[:password])
			if params[:remember_me]
			cookies.permanent[:auth_token]
			else
			cookies[:auth_token]
			end
			redirect_to root_url
		else
			flash.now.alert = "Invalid email or password!"
			render "signup"
		end
	end
	
	def destroy
		cookies.delete[:auth_token]
		redirect_to root_url
	end
	
end

Application Controller:

class ApplicationController < ActionController::Base
  protect_from_forgery

  private

  def current_user
    @current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
    end

  helper_method :current_user
end

Relevant element of my pages#home page:

<div class="login">
					<li><% if :current_user %>
  						<!-- user is logged in -->
  						<%= link_to "Logout", logout_path %>
						<% else %>
  					<!-- user is not logged in -->
  						<%= link_to "Login", login_path %>
						/
						<%= link_to "Sign up", signup_path %>
					<% end %></li>
				</div>

Routes File:

MadeByV2::Application.routes.draw do

    get "sessions/new"
    get "logout" => "sessions#destroy"

    controller :pages do
    get "home" => "pages#home"
    get "about" => "pages#about"
    get "contact" => "pages#contact"
    end

    controller :user do
    get "signup" => "user#new"
    end

    resources :users, :controller => 'user'

    controller :sessions do
      get "login" => "sessions#new"
      post "login" => "sessions#create"
      delete "logout" => "sessions#destroy"
    end

   root :to => "pages#home"

end

I can’t seem to work out at all how to the log out button to work correctly and also ensure that in Pages#Home Logout is shown if a user is logged in and Log in/ Sign up is shown if a user isn’t logged in.

Any help people can offer really would be much appreciated.

Thanks in Advance,
Tom

Going to try and walk through the code you have posted here. I’m assuming this is the most recent version.

 
class SessionsController < ApplicationController
    def new
    end
    
    def create
        user = User.find_by_email(params[:email])
        if user && user.authenticate(params[:password])
            if params[:remember_me]
            cookies.permanent[:auth_token]
            else
            cookies[:auth_token]
            end
            redirect_to root_url
        else
            flash.now.alert = "Invalid email or password!"
            render "signup"
        end
    end
    
    def destroy
        cookies.delete[:auth_token]
        redirect_to root_url
    end
    
end

This looks fine, however, I don’t see any code to generate your auth_token. Do you have something in your user model to do that? I would have a method that looks something like this:

def generate_token(column)
  begin
    self[column] = SecureRandom.urlsafe_base64
  end while User.exists?(column => self[column])
end

And then your sessions controller create method would look like this (only slightly different):

    def create
        user = User.find_by_email(params[:email])
        if user && user.authenticate(params[:password])
            if params[:remember_me]
            cookies.permanent[:auth_token] = user.auth_token
            else
            cookies[:auth_token] = user.auth_token
            end
            redirect_to root_url
        else
            flash.now.alert = "Invalid email or password!"
            render "signup"
        end
    end

class ApplicationController < ActionController::Base
  protect_from_forgery
  
  private
  
  def current_user
    @current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
    end
    
  helper_method :current_user
end

I would rearrange this so that you call protect_from_forgery, register your helper methods, and then call private, then your actual helper methods. Hope that makes sense.


<div class="login">
  <li><% if :current_user %>
    <!-- user is logged in -->
   <%= link_to "Logout", logout_path %>
                        <% else %>
                      <!-- user is not logged in -->
                          <%= link_to "Login", login_path %>
                        /
                        <%= link_to "Sign up", signup_path %>
                    <% end %></li>
                </div>

This isn’t going to work. On line two of this snippet is the problem. (The HTML here is also a little strange to me.) You are calling a symbol by using :current_user write it like this:


<div class="login">
 <% if current_user %>
   Hi, <%= user.email %>! <br />
   <%= link_to "Logout", logout_path %> <% else %> <%= link_to "Login", login_path %> or <%= link_to "Sign up", signup_path %>
<% end %>
</div>


Your routes file looked fine to me. Though I didn’t look really close at them. The error you were receiving was because you were trying to call a symbol as opposed to the actual current_user method.

Good luck and continue to ask questions of you need further help!

Hi Scannon,

Thanks so much for your reply. Unfortunately having gone through and updated my code with your suggestions I am still finding when I login and am forwarded back to my root_url that Login or Sign up is still being displayed. I can only assume it’s not saving the session properly because having checked the Sqlite database the auth_token fields are definitely filling correctly when a user signs up.

Is there something I can do to test whether the session controller is working correctly.

My current setup is as follows:

Pages#Home

<div class="login">
 					<% if current_user %>
   						Hi, <%= user.email %>! <br />
   					<%= link_to "Logout", logout_path %>
					<% else %> <%= link_to "Login", login_path %> or <%= link_to "Sign up", signup_path %>
					<% end %>
				</div>

Sessions Controller

class SessionsController < ApplicationController
	def new
	end
	
	def create
		user = User.find_by_email(params[:email])
		if user && user.authenticate(params[:password])
			if params[:remember_me]
			cookies.permanent[:auth_token]
			else
			cookies[:auth_token]
			end
			redirect_to root_url
		else
			flash.now.alert = "Invalid email or password!"
			render "signup"
		end
	end
	
	def destroy
		cookies.delete[:auth_token]
		redirect_to root_url
	end
	
end

User Model

class User < ActiveRecord::Base
	has_secure_password
	before_create { generate_token(:auth_token) }
	
	validates_presence_of :password, :on => :create
	validates_presence_of :email
	validates_length_of :email, :within => 6..50
  	validates_length_of :password, :within => 6..30
	validates_uniqueness_of :email, :case_sensitive => false, :on => :create
	validates_format_of :email,    :with => /^[A-Z0-9_.%-]+@([A-Z0-9_]+\\.)+[A-Z]{2,4}$/i,
									:message => "must be a valid e-mail address"

def generate_token(column)
	begin
		self[column] = SecureRandom.urlsafe_base64
	end while User.exists?(column => self[column])
end

end

Application Controller

class ApplicationController < ActionController::Base
  protect_from_forgery

  private

  def current_user
    @current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
    end

  helper_method :current_user
end

This is really confusing me as I can’t see anything wrong myself.

Any help you can offer would be awesome :slight_smile:

Thanks,
Tom

Alright a couple of things still wrong.

First, your sessions controller CREATE method needs to be changed.

This line

cookies.permanent[:auth_token] 

needs to be changed to this

cookies.permanent[:auth_token] = user.auth_token

also this line…

cookies[:auth_token]

needs to be changed to this

cookies[:auth_token] = user.auth_token

(See my first post to copy and paste the create method code.)

Second, your application controller should be ordered like this…



class ApplicationController < ActionController::Base
  protect_from_forgery
  
  helper_method :current_user


  private
  
  def current_user
    @current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
   end


end


Now for a quick explanation of what I think is happening here. The session isn’t being created properly because of the two lines that I pointed out. Basically you aren’t telling the cookie what it should contain. By saying cookies.permanent[:auth_token] = user.auth_token you are telling the cookie to save the current users auth token in the cookie. Without the equals the cookie contains nothing.

As far as testing things that is beyond the scope of this post, but TDD (test driven development) and BDD (behaviour driven development) are VERY prevalent within the Ruby and Rails communities. There are literally tons of testing frameworks and ideas behind how it should be done. Do some googling and make decisions for yourself.

Personally, if you are planning on doing any kind of extensive work with Rails you will eventually need to have a solid grasp of Ruby. I recommend you check out Chris Pines “Learn to Program, 2nd Ed.” and Zed Shaw’s “Learn Ruby the Hard Way.” Both will help you with understanding Ruby and programming basics. Obviously, I don’t know what kind of programming experience you have but these two resources will be invaluable to you if you are just starting out.

Good luck, and let us know if this works! :smiley:

Hey again scannon,

Thanks so much for all your help here. You will be pleased to hear that it is all working :smiley:

The only issue I am having now is that the cookies aren’t being destroyed and the browser redirected to the root URL when hitting Logout.

Instead I am getting the following error:

wrong number of arguments (0 for 1)
Rails.root: C:/Sites/Made_By_v2

Application Trace | Framework Trace | Full Trace
app/controllers/sessions_controller.rb:21:in `destroy'

Would this be to do with the fact it can’t find the :auth_token?

Session controller is as follows:

class SessionsController &lt; ApplicationController
	def new
	end
	
	def create
		user = User.find_by_email(params[:email])
		if user && user.authenticate(params[:password])
			if params[:remember_me]
			cookies.permanent[:auth_token] = user.auth_token
			else
			cookies[:auth_token] = user.auth_token
			end
			redirect_to root_url
		else
			flash.now.alert = "Invalid email or password!"
			render "signup"
		end
	end
	
	def destroy
		cookies.delete[:auth_token]
		redirect_to root_url
	end
	
end

Thanks again so much for your help :slight_smile:

Tom

The following line in your destroy method needs to be changed:

cookies.delete[:auth_token]

to

cookies.delete(:auth_token)

It should also then be able to take you to the root url, which in your case, is pages#home.

Amazing! It’s finally fully working logging in and out and holding onto the cookies, thank you so much for all your help! :slight_smile:

Just for those that find this post and are interested the final solution from scannon was as follows.

Sessions Controller

class SessionsController < ApplicationController
	def new
	end
	
	def create
		user = User.find_by_email(params[:email])
		if user && user.authenticate(params[:password])
			if params[:remember_me]
			cookies.permanent[:auth_token] = user.auth_token
			else
			cookies[:auth_token] = user.auth_token
			end
			redirect_to root_url
		else
			flash.now.alert = "Invalid email or password!"
			render "signup"
		end
	end
	
	def destroy
		cookies.delete(:auth_token)
		redirect_to root_url
	end
	
end

Application controller

class ApplicationController < ActionController::Base
  protect_from_forgery

  helper_method :current_user


  private

  def current_user
    @current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
   end


end

User Model

class User < ActiveRecord::Base
	has_secure_password
	before_create { generate_token(:auth_token) }
	
	validates_presence_of :password, :on => :create
	validates_presence_of :email
	validates_length_of :email, :within => 6..50
  	validates_length_of :password, :within => 6..30
	validates_uniqueness_of :email, :case_sensitive => false, :on => :create
	validates_format_of :email,    :with => /^[A-Z0-9_.%-]+@([A-Z0-9_]+\\.)+[A-Z]{2,4}$/i,
									:message => "must be a valid e-mail address"

def generate_token(column)
	begin
		self[column] = SecureRandom.urlsafe_base64
	end while User.exists?(column => self[column])
end

end

User new

<div id="signup-area">
	<h1>Sign Up</h1>
<div class="signup-fields">
<%= form_for @user do |f| %>

	<% if @user.errors.any? %>
		<div class="error_messages">
		 <h2>Form is invalid</h2>
			<ul>
				<% for message in @user.errors.full_messages %>
					<li><%= message %></li>
				<% end %>
			</ul>
		</div>
	<% end %>	
	<div class = "field">
		<%= f.label :email %>
		<%= f.text_field :email %>
	</div>
	
	<div class = "field">
		<%= f.label :password %>
		<%= f.password_field :password %>
	</div>
	
	<div class = "field">
		<%= f.label :password_confirmation %>
		<%= f.password_field :password_confirmation %>
	</div>
	
	<div class="actions"><%= f.submit "Submit" %></div>
</div>
<% end %>
</div>		

Sessions New

<div id="login-area">
<div id="title">
	<h1>Log In</h1>
</div>

<%= form_tag login_path do %>
<div class="login-fields">
	<div class="field">
			<%= label_tag :email %>
		
			<%= text_field_tag :email, params[:email] %>
	</div>
	
	<div class ="field">
		<%= label_tag :password %>
		<%= password_field_tag :password %>
	</div>
	
	<div class="actions"><%= submit_tag "Log in" %></div>
</div>
<%end%>
<div class = "field">
	<%= label_tag :remember_me %>
	<%= check_box_tag :remember_me, 1, params[:remember_me] %>
</div>
<div class="Not-registered">
	<p>Not yet registered? <%= link_to "Sign up here", signup_path %></p>
</div>	
</div>

Pages#Home login and logout element

<div class="login">
 					<% if current_user %>
   						Hi, <%= current_user.email %>! <br />
   					<%= link_to "Logout", logout_path %>
					<% else %> <%= link_to "Login", login_path %> or <%= link_to "Sign up", signup_path %>
					<% end %>
				</div>

Thanks again scannon for some amazing help!

Tom :smiley:

The only issue I am having now is that the cookies aren’t being destroyed and the browser redirected to the root URL when hitting Logout.

When you hit “Log Out” the session is destroyed and the cookie for the “Remember Me” feature is deleted. Meaning that when you revisit the site, you should have to supply your log in credentials. What, exactly, is the unexpected behavior that you are experiencing?

With the redirect, it is as simple as changing the following line in the Sessions Controller destroy method:

redirect_to root_url

Just change the root_url to whatever path name you want.