Creating a Rails API With Jason Web Token (JWT) Authentication

Techie     September 2022

Introduction

Token based authentication is an alternative method to session-based authentication. The server creates a web token (JWT), encodes, serializes and signs it with its own secret key so that when it’s tampered with, the server will know and reject it.

Because the JWT created contains all the information about the user, it is sent to the browser, and so the server does not need to store information about the user.

Let’s create a Rails API that authenticates users with JWT.


Prerequisites


Create the Project

1 . Create a rails API only project.

$ rails new jwt_api --api -d mysql


2 . Add JSON Web Token (JWT) and bcrypt gem in Gemfile.

# ...

# Use JSON Web Token (JWT) for token based authentication
gem "jwt"

# Use ActiveModel has_secure_password
gem "bcrypt", "~> 3.1.7"


3 . Bundle up

$ bundle install


4 . Create the database

$ rails db:create


5 . Create the user model

$ rails g model user name:string username:string email:string password_digest:string

$ rails db:migrate


6 . Require securerandom for generating session keys.

# app/models/user.rb

class User < ApplicationRecord
  require "securerandom"
 
  has_secure_password
 
  validates :email, presence: true
  validates :password, presence: true
  validates :username, presence: true, uniqueness: true
 
end


7 . Generate users controller.

$ rails g controller users


8 . Add actions in users controller.

class UsersController < ApplicationController
  skip_before_action :authenticate_request, only: [:create]
  before_action :set_user, only: [:show, :destroy]
  
  # GET /users
  def index
    @users = User.all
    render json: @users, status: :ok
  end

  
  # GET /users/{username}
  def show
    render json: @user, status: :ok
  end
  
 
  # POST /users
  def create
    @user = User.new(user_params)
    if @user.save
      render json: @user, status: :created
    else
      render json: { errors: @user.errors.full_messages }, 
             status: :unprocessable_entity
    end
  end
  
  
  # PUT /users/{username}
  def update
    unless @user.update(user_params)
      render json: { errors: @user.errors.full_messages },
             status: :unprocessable_entity
    end
  end
  
  
  # DELETE /users/{username}
  def destroy
    @user.destroy
  end
  
  
  private
    def user_params
      params.permit(:username, :name, :email, :password)
    end
  
  
    def set_user
      @user = User.find(params[:id])
    end
    
end



9 . Create JsonWebToken concerns in app/controllers/concerns.

# app/controllers/concerns/json_web_token.rb

require "jwt"
module JsonWebToken
  extend ActiveSupport::Concern
  SECRET_KEY = Rails.application.secret_key_base
  
  
  def jwt_encode(payload, exp = 7.days.from_now)
    payload[:exp] = exp.to_i
    JWT.encode(payload, SECRET_KEY)
  end


  def jwt_decode(token)
    decoded = JWT.decode(token, SECRET_KEY)[0]
    HashWithIndifferentAccess.new decoded
  end
  
end


10 . Create authenticate_request method in app/controllers/application_controller.rb.

class ApplicationController < ActionController::API
  include JsonWebToken
  
  before_action :authenticate_request
  
  private
    def authenticate_request
      header = request.headers["Authorization"]
      header = header.split(" ").last if header
      decoded = jwt_decode(header)
      @current_user = User.find(decoded[:user_id])
    end

end


11 . Create authentication controller and add the login method.

$ rails g controller authentication


# app/controllers/concerns/authentication_controller.rb

class AuthenticationController < ApplicationController
  skip_before_action :authenticate_request
  
  
  # POST /auth/login
  def login
    @user =User.find_by_email(params[:email])
    if @user&.authenticate(params[:password])
      token = jwt_encode(user_id: @user.id)
      render json: { token: token }, status: :ok
    else
      render json: { error: 'unauthorized' }, status: :unauthorized
    end 
  end

end


12 . Update routes

Rails.application.routes.draw do  
  resources :users
  post "/auth/login", to: "authentication#login" 
end


Test the application via postman

1 . Create a user

Notice the response containing the created user data at the bottom.

missing image


2 . Login

Notice the response containing the authorization token at the bottom.

missing image


Thanks for reading, see you in the next one!