Implementing a simple JWT authentication in a rails application with less configuration. The idea is to have a middleware that checks token in the request headers object to verify token before allowing user access to secure controller methods.
Here we've sessions controller
responsible for LOGIN
and SIGNUP
, and a todos controller
to perform CRUD.
but first, let's create a new rails API by running
rails new todoApi --api
and generate our models
#Users model
rails generate model user email:string password:string
#Todos model
rails generate model todo title:string
# run migration
rails db:migrate
and install jwt by adding
gem 'jwt'
to Gemfile
and inside application_controller.rb
we define our authentication methods since all controllers inherit from application controller
class ApplicationController < ActionController::API
SECRET = "yoursecretword"
def authentication
# making a request to a secure route, token must be included in the headers
decode_data = decode_user_data(request.headers["token"])
# getting user id from a nested JSON in an array.
user_data = decode_data[0]["user_id"] unless !decode_data
# find a user in the database to be sure token is for a real user
user = User.find(user_data&.id)
# The barebone of this is to return true or false, as a middleware
# its main purpose is to grant access or return an error to the user
if user
return true
else
render json: { message: "invalid credentials" }
end
end
# turn user data (payload) to an encrypted string [ A ]
def encode_user_data(payload)
token = JWT.encode payload, SECRET, "HS256"
return token
end
# turn user data (payload) to an encrypted string [ B ]
def encode_user_data(payload)
JWT.encode payload, SECRET, "HS256"
end
# decode token and return user info, this returns an array, [payload and algorithms] [ A ]
def decode_user_data(token)
begin
data = JWT.decode token, SECRET, true, { algorithm: "HS256" }
return data
rescue => e
puts e
end
end
# decode token and return user info, this returns an array, [payload and algorithms] [ B ]
def decode_user_data(token)
begin
JWT.decode token, SECRET, true, { algorithm: "HS256" }
rescue => e
puts e
end
end
end
now let's add a few configuration to our routes
and sessions controller
in routes.rb
Rails.application.routes.draw do
post "/login", to: "sessions#login"
post "/signup", to: "sessions#signup"
resources :todos
end
in sessions_controller.rb
class SessionsController < ApplicationController
def signup
user = User.new(email: param[:email], password: password[:password])
# if user is saved
if user.save
# we encrypt user info using the pre-define methods in application controller
token = encode_user_data({ user_data: user.id })
# return to user
render json: { token: token }
else
# render error message
render json: { message: "invalid credentials" }
end
end
def login
user = User.find_by(email: param[:email])
# you can use bcrypt to password authentication
if user && user.password == param[:password]
# we encrypt user info using the pre-define methods in application controller
token = encode_user_data({ user_data: user.id })
# return to user
render json: { token: token }
else
render json: { message: "invalid credentials" }
end
end
end
we can now secure controller methods by using a controller callback and authentication
method in application_controller.rb
class TodosController < ApplicationController
# authentication is the method we define in application_controller.rb to check request.headers['token']
before_action :authentication
# GET /todos
def index
@todos = Todo.all
render json: @todos
end
end
easy peasy..
Top comments (5)
Hello !
Thank you for your article !
But I'm wondering about something in your authentication function from ApplicationController.
I may be wrong but you're not supposed to call the database here. JWT was created to avoid to call the database when the user have to be authorized.
(this function should maybe named authorization ? Because the client provide a token, not credentials)
A REST API can be called by a Human or an Application. It's maybe not a good idea to call the User model anyway.
Thank you this article again ! I'll follow you on dev.to :D
Hi. It is different to go to the database to fetch the existing of a token (that usually is sent from front end to back end and then matched to db), than looking up an index integer PK, aka the user id. Because it's an integer lookup as well as this happening on an indexed column is much fast than doing string comparison against tokens.
So, it's not the same as session authentication with a DB-stored token. it's much faster, and it's anyway the only way to fetch the user on the backend once receiving the JWT. After all, there should no be private information on JWT (e.g. email) since it is just encoded and not encrypted and it can be decoded by anyone.
Thank you so much, yes it a complex app itโs not a good advice to do this. But in this case We need to know if the user really in the database
I was struggling setting up authentication for my app but you make it so easy
Keep up the good work
Thank you ๐