Open app/controllers/sessions_controller.rb
and add the following lines
1class SessionsController < ApplicationController
2 before_action :authenticate_user_using_x_auth_token, only: [:destroy]
3
4 def create
5 user = User.find_by(email: login_params[:email].downcase)
6 if user.present? && user.authenticate(login_params[:password])
7 render status: :ok, json: { auth_token: user.authentication_token,
8 userId: user.id,
9 user_name: user.name }
10 else
11 render status: :unauthorized, json: { notice: t('session.incorrect_credentials') }
12 end
13 end
14end
15
16 def destroy
17 @current_user = nil
18 end
19
20 private
21
22 def login_params
23 params.require(:login).permit(:email, :password)
24 end
25end
Let's modify routes.rb
and add a destroy action for session
:
1Rails.application.routes.draw do
2 resources :tasks, except: %i[new edit]
3 resources :users, only: :create
4 resource :sessions, only: [:create, :destroy]
5
6 root "home#index"
7 get '*path', to: 'home#index', via: :all
8end
9
If you are wondering what the %i[]
notation is, then it's nothing but a non-interpolated array of symbols, separated by white-space. If we want to group n-number of symbols in an array format, then use %i[symbol1 symbol2]
notation. It saves you from adding a :
symbol for each of the symbols like we have written the sessions resource above in routes.rb
. Also it's much more neat that way and helps with readability. Also in the routes, if you look closely, you will notice that the tasks
resources is written with an except
rather than using only
. As a general rule of thumb, always prefer using except
if the number of items in only
is greater than 3. This is also to make the code cleaner.
Now we will be making a DELETE
request to the route /sessions
and passing authentication_token
and email
in the request header, to you know, verify that's it's actually us who is making the request.
Open apis/auth.js
and add following lines.
1import axios from "axios";
2
3const login = payload => axios.post("/sessions", payload);
4
5const logout = () => axios.delete(`/sessions`);
6
7const signup = payload => axios.post("/users", payload);
8
9const authApi = {
10 login,
11 logout,
12 signup,
13};
14
15export default authApi;
Let's open apis/axios.js
and create a function to clear the default Axios headers when the user is logged out. Add the following code at the end of the file:
1export const resetAuthTokens = () => {
2 delete axios.defaults.headers["X-Auth-Email"];
3 delete axios.defaults.headers["X-Auth-Token"];
4};
Before we add code to the application view file, let's create navigation components.
Let's open the NavBar/index.jsx
component and make use of the logout API that we added before to make the logout logic complete:
1 //------previous code -------
2
3import { getFromLocalStorage, setToLocalStorage } from "helpers/storage";
4
5const NavBar = () => {
6
7 //------previous code -------
8
9 const handleLogout = async () => {
10 try {
11 await authApi.logout();
12 setToLocalStorage({ authToken: null,
13 email: null,
14 userId: null,
15 userName: null });
16 resetAuthTokens();
17 window.location.href = "/";
18 } catch (error) {
19 logger.error(error);
20 }
21 };
22
23 return (
24
25 //------previous code -------
26
27 <a
28 onClick={handleLogout}
29 className="inline-flex items-center px-1 pt-1 text-sm
30 font-semibold leading-5 text-bb-gray-600 text-opacity-50
31 transition duration-150 ease-in-out border-b-2
32 border-transparent hover:text-bb-gray-600 focus:outline-none
33 focus:text-bb-gray-700 cursor-pointer"
34 >
35 LogOut
36 </a>
37 </div>
38 </div>
39 </div>
40 </nav>
41
42 );
43};
44
45export default NavBar;
Once the logout
button(or the simple anchor tag) is clicked, the logout API is called and all the data stored in local storage is cleared. We are making use of the setToLocalStorage
helper method created in the previous section. The user will be then redirected to the login page if the logout was successfully done in server too, where we just destroy the @current_user
instance variable.
Let's add a new column creator_id
to tasks table.
To add a new column to our existing table, we'll generate a new migration.
1rails g migration AddCreatorIdToTask creator_id:integer
1class AddCreatorIdToTask < ActiveRecord::Migration[6.0]
2 def change
3 add_column :tasks, :creator_id, :integer
4 end
5end
The code for the change
method will be auto-populated when we run the above command. The reason is that, our migration command is of form add_column_name_to_table_name column_name:type
which Rails will infer from and hence generates the code for that, even respecting that types passed in. Cool right? That's one among the many Rails magic that you will be seeing throughout this book!
Again, once we create a new migration, let's persist that into our database:
1bundle exec rails db:migrate
We always want the creator_id
of a task to be set as the id
of the current_user
, because the current user is the one who creates the task.
To implement that, we need to change the create
action of Tasks
controller, as follows:
1class TasksController < ApplicationController
2 before_action :authenticate_user_using_x_auth_token
3 before_action :load_task, only: %i[show update destroy]
4
5 # ----- previous code ------
6
7 def create
8 @task = Task.new(task_params.merge(creator_id: @current_user.id))
9 if @task.save
10 render status: :ok, json: { notice: t('successfully_created', 'Task') }
11 else
12 errors = @task.errors.full_messages.to_sentence
13 render status: :unprocessable_entity, json: { errors: errors }
14 end
15 end
16
17 # ----- previous code ------
18
19 private
20
21 def task_params
22 params.require(:task).permit(:title)
23 end
24
25 # ----- previous code ------
26end
27
With this change, the created task always has its creator_id
set to that of the current_user
.
Now let's commit these changes:
1git add -A
2git commit -m "Added ability to logout"