Signing out

Adding sign out

Phew, with all this focus on security, we almost forgot to implement the sign out feature! Let's do it now, it's going to be much easier than the previous chapters.

In the routes file, we can add a new destroy route to the sessions controller:

# config/routes.rb

Rails.application.routes.draw do
  # All the previous routes
  resource :session, only: %i[new create destroy]
end

In the sessions controller, let's add the destroy action:

# app/controllers/sessions_controller.rb

def destroy
  sign_out
  redirect_to root_path, notice: "You have been signed out"
end

We then simply need to implement the sign_out method in the Authentication concern:

# app/controllers/concerns/authentication.rb

def sign_out
  session_from_cookies.destroy!
  cookies.delete(:session_id)
end

In this method, we try to find the session from the cookies and destroy it if it exists. We then delete the session_id cookie.

Let's add a button to our navbar to test this feature:

<!DOCTYPE html>
<html>
  <head>
    <%# All the previous code %>
  </head>

  <body>
    <header>
      <nav>
        <ul>
          <li><%= link_to "Home", root_path %></li>
          <li><%= link_to "Dashboard", dashboard_path %></li>
          <li><%= link_to "Sign in", new_session_path %></li>
          <li><%= link_to "Sign up", new_registration_path %></li>
          <li><%= button_to "Sign out", session_path, method: :delete %></li>
        </ul>
      </nav>
    </header>

    <main>
      <%# All the previous code %>
    </main>
  </body>
</html>

If we now go to the browser and sign in with the first user, we should be redirected to the dashboard page. When we click on the "Sign out" button, we should be redirected to the home page and see the "You have been signed out" notice. Clicking on the "Dashboard" link should redirect us to the sign-in page with the message "You must be signed in to perform that action".

Authorization

Some of the features we just implemented require users to be signed in. For example, if you are not signed in and try to sign out, you will get an error because session_from_cookies.destroy! will fail as session_from_cookies is nil.

To fix this, let's require authentication for the #destroy action of the SessionsController:

# app/controllers/sessions_controller.rb

class SessionsController
  before_action :require_authentication, only: :destroy

  # All the previous code
end

If we now click on the "Sign out" button in the browser while not signed in, we should be redirected to the sign-in page with the message "You must be signed in to perform that action".

Similarly, we shouldn't be able to sign up or sign in if we are already signed in. Let's prevent that by adding a before_action in the SessionsController:

# app/controllers/sessions_controller.rb

class SessionsController
  before_action :redirect_if_signed_in, only: %i[new create]

  # All the previous code
end

We can add the same before_action in the RegistrationsController:

# app/controllers/sessions_controller.rb

class SessionsController
  before_action :redirect_if_signed_in, only: %i[new create]

  # All the previous code
end

To share the redirect_if_signed_in method between the two controllers, we can implement it in the Authentication concern:

# app/controllers/concerns/authentication.rb

module Authentication
  # All the previous code

  def redirect_if_signed_in
    if restore_authentication
      redirect_to root_path, notice: "You are already signed in"
    end
  end
end

If we now test in the browser, we should see the "You are already signed in" notice when trying to sign in or sign up while already signed in.