Authentification with JWT in Rails

Authentification with JWT in Rails

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 routesto 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.