In this article, I will show you how to implement authentication with JSON Web Token in Rails. let's look at what JWT is all about.
JSON Web Token
JWT stands for JSON Web Token. It is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. JWTs are commonly used for authentication and authorization purposes in web applications and APIs. A JWT consists of three parts: a header, a payload, and a signature. The header contains metadata about the token, such as the algorithm used for signing the token. The payload contains the claims or statements about the user or entity being authenticated. The signature is used to verify the authenticity of the token.
For more information about JWT, you can visit this link
We will be creating a signup and login API endpoint so that when a user signup or login, a token is created for that user. That means the user won’t need to signup and login before a token is created. Let's begin
step 1
create our application with rails as follow
#terminal
rails new backend-jwt --api --database=postgresql
— api to let rails know that we will be creating api for this project and — database=postgrsql to specify the database we will be using.
Add JWT to your gemfile
#gemfile
gem 'jwt'
Run this command in the terminal to install all the dependencies needed for the project.
bundle install
Let's create a controller and model for the user
#terminal
rails generate scaffold user name:string email:string
This automatically generates a controller with all the methods, model, and migration files for the user. It also created routes for us to access the user.
Let's create JWT class
open the concern folder inside the controller folder and create a file called
json_web_token.rb and insert the code below.
class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
def self.encode(payload, exp = 7.days.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
end
end
SECRET_KEY
is a constant that holds the secret key used for encoding and decoding tokens. It is derived from the Rails application's secret key base. SECRET_KEY must be secret and not to be shared.
self.encode
is a class method that takes a payload (a hash of data) and an expiration time (defaulting to 7 days from the current time). The payload hash is modified to include an “exp” key with the expiration time converted to an integer. JWT.encode
is called with the modified payload and the secret key to generate the token.
self.decode
is a class method that takes a JWT token. JWT.decode
is called with the token and the secret key to decode the JWT. The decoded token is returned as an array, and we extract the first element.
Create authenticate_request function
def authenticate_request
header = request.headers['Authorization']
header = header.split(' ').last if header
begin
@decoded = JsonWebToken.decode(header)
@current_user = User.find(@decoded[:user_id])
rescue ActiveRecord::RecordNotFound => e
render json: { errors: e.message }, status: :unauthorized
rescue JWT::DecodeError => e
render json: { errors: e.message }, status: :unauthorized
end
end
end
The function expects a token to be present in the request header, with the key ‘Authorization’. The token is decoded to retrieve the payload value and in this application, the payload value includes the user_id
.
If there’s an error during the decoding process, such as an expired or invalid token, a JWT::DecodeError
will be raised.
After obtaining the user_id
from the payload, the function attempts to find the corresponding user in the database. If the user does not exist, an ActiveRecord::RecordNotFound
exception is raised.
Let's create authenticate controller
The purpose of this controller is to handle the login logic
#terminal
rails g controller authenticate
Inside the controller copy and paste the code below
class AuthenticateController < ApplicationController
before_action :authenticate_request, except: :login
# POST /login
def login
@user = User.find_by(login_params)
if @user
token = JsonWebToken.encode(user_id: @user.id)
time = Time.now + 7.days.to_i
render json: { token: token, exp: time.strftime('%m-%d-%Y %H:%M'),
user: @user }, status: :ok
else
render json: { error: 'unauthorized' }, status: :unauthorized
end
end
private
def login_params
params.permit(:name, :email)
end
end
Also, modify the create method in the user controller with the code below
#user_controller
def create
@user = User.new(user_params)
if @user.save
token = JsonWebToken.encode(user_id: @user.id)
time = Time.now + 7.days.to_i
render json: { token: token, exp: time.strftime('%m-%d-%Y %H:%M'),
user: @user }, status: :ok
else
render json: { errors: @user.errors.full_messages },
status: :unprocessable_entity
end
end
Let's create the route for the login
Open your routes.rb in the config folder and set the login route as below.
Rails.application.routes.draw do
resources :users
post 'login', to: 'authenticate#login'
end
You can run rails routes
to see all the available endpoints but we will focus on just 3 of them which are
users GET /users(.:format) users#index
POST /users(.:format) users#create
login POST /login(.:format) authenticate#login
With all that been done, let's test our API endpoints
I will be using a vs code extension called thunder client to test it. You can also use Postman to do it.
Let's start our server
rails s
let's test the endpoint to create a new user
As we can see the token has been created and for us to query our database, we need to pass the token in the header of our request. Let's query the database to get all the users
Copy the generated token and go to the Auth. Inside the Auth select Bearer and paste the token inside as shown below.
You can now make your request with the get HTTP method as shown below.
let's test the login endpoint
Remove the token from the Auth Bearer so that we won't have a token yet.
we are going to pass in the login credentials as shown below
As you can see, the token is also generated when the user login.
We have implemented JWT in our application.