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 < ApplicationController
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 < ApplicationController
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/registrations_controller.rb
class RegistrationsController < ApplicationController
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.