1
votes

Following the ruby on rails tutorial (https://www.railstutorial.org/book/log_in_log_out). The logout feature isn't working. I generated a 'sessions' controller and defined a method destroy to 'log_out' the 'current_user' which is an instance variable and is changed to nil when the log_out method is called. 'log_out' method in the sessions_helper.rb.

module SessionsHelper
def log_in(user)
    session[:user_id] = user.id
end

def remember(user)
  user.remember 
  cookies.permanent[:remember_token] = user.remember_token
  cookies.permanent.signed[:user_id] = user.id
end

def current_user
    if (user_id = session[:user_id])
        @current_user ||= User.find_by(id: session[:user_id])
    elsif (user_id = cookies.signed[:user_id])
        user = User.find_by(id: user_id)
        if user && user.authenticated?(cookies:[:remember_token])
            log_in user
            @current_user = user
        end
    end
end

def logged_in?
    !current_user.nil?
end

def log_out
    debugger
    session.delete(:user_id)
    @current_user = nil
 end
end

I use a DELETE request to '/logout'.

 delete 'logout' => 'sessions#destroy'

The relevant part of the layout in the views

<% if logged_in? %>
    <ul class="text-center nav navbar-nav navbar-right">
        <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Account <span class="caret"></span></a>
        <ul class="text-center dropdown-menu">
            <li class="text-center"><%= link_to "Profile", current_user %></li>
            <li class="text-center"><%= link_to "Settings", "#" %></li>
            <li role="separator" class="divider"></li>
            <li class="text-center"><%= link_to "Log Out", logout_path, :method => :delete %></li>
            </ul>
        </li>
    </ul>
    <% else %>
    <li><a id="signin" href="/login">Sign In</a></li>
<% end %>

I believe the log_out method is not working because the view doesn't change, which according to the line <% if logged_in? %>, the logged_in method should prevent the part of view to be rendered when the session is deleted. Where 'logged_in?' method is defined in sessions_helper.rb that returns a boolean value true if the user is logged in.

Entire routes.rb

Rails.application.routes.draw do

    root 'static_pages#home'
    get 'about' => 'static_pages#about'
    get 'help' => 'static_pages#help'
    get 'signup' => 'users#new'
    get  'login' => 'sessions#new'
    post  'login' => 'sessions#create'
    delete 'logout' => 'sessions#destroy'
    resources :users
end

Entire sessions_controller.rb

class SessionsController < ApplicationController

def new
end

def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
        log_in user
        remember user
        redirect_to user
    else
        flash.now[:danger] = 'Invalid email/password combination'
        render 'new'
    end
end

def destroy
    log_out
end
end

Entire sessions_helper.rb

module SessionsHelper
def log_in(user)
    session[:user_id] = user.id
end

def remember(user)
  user.remember 
  cookies.permanent[:remember_token] = user.remember_token
  cookies.permanent.signed[:user_id] = user.id
end

def current_user
    if (user_id = session[:user_id])
        @current_user ||= User.find_by(id: session[:user_id])
    elsif (user_id = cookies.signed[:user_id])
        user = User.find_by(id: user_id)
        if user && user.authenticated?(cookies:[:remember_token])
            log_in user
            @current_user = user
        end
    end
end

def logged_in?
    !current_user.nil?
end

def log_out
    session.delete(:user_id)
    @current_user = nil
end
end
1

1 Answers

10
votes

The proper way to log a user out in Rails is by invalidating the session.

def log_out
  reset_session
  @current_user = nil
end

The sessions work in Rails is that the visitor is issued a cookie with a session id (a hash) when they first visit the site. This is linked to a stored session (also a cookie) and rails keeps track of which session ids are valid.

reset_session invalidates the session id on the server which is very important if you want to avoid things like session fixation and replay attacks. It also issues a new session id.

Doing session.delete(:user_id) only manipulates the session storage cookie held by the client. So if the client for example sends an older cookie they would still be logged in!

Then why is it not in the tutorial?

M. Hartl's Rails Tutorial book is not officially sanctioned and while its pretty good at explaining the key concepts it contains quite a lot which is very questionable.