Authentication with JWT

Photo by Franck on Unsplash

Psst, this is a continuation of my previous blog post, Invalid Authenticity Token.

JSON Web Tokens, or JWT, are a modern solution to transferring secure information between parties. Each token is signed using the unique identifier of the sender, cross-checking for any alterations.

JWT saved me from having to rewrite an entire frontend in Rails just to ensure user security. All I had to do was implement it with an expiration to guarantee further protection. Below, I’ll go through how I implemented the gem!

Installing

Installation is simple — just add gem 'jwt' to your Gemfile and run bundle install in your terminal.

Backend

Sessions Controller

  1. Create a SessionsController and make sure to set the HMAC secret and algorithm type so that we can use this in the future.
  2. We have two methods using JWT, encode and decode. encode takes in a user_id argument, creates a payload hash, and uses the built-in JWT encode method to set the payload, the secret, and algorithm type that we set earlier. decode takes in a token and uses it with the built-in JWT decode method. Since the information we want is nested, the decoded_token[0][‘user_id’] line retrieves from the actual token.
  3. In the create method, we can then use the encode method we defined intoken = SessionsController.encode(user.id).
class SessionsController < ApplicationController    HMAC_SECRET = ENV['HMAC_SECRET']    ALGORITHM_TYPE = 'HS256'    def self.encode(user_id)        payload = {user_id: user_id}        JWT.encode(payload, HMAC_SECRET, ALGORITHM_TYPE)    end    def self.decode(token)        decoded_token = JWT.decode(token, HMAC_SECRET, true, {algorithm: ALGORITHM_TYPE})        decoded_token[0]['user_id']    end    def create        user = User.find_by(email: session_params[:email])        if user && user.authenticate(session_params[:password])            token = SessionsController.encode(user.id)            session[:username] = user.username            session[:token] = token            session[:user_id] = user.id            render json: {                logged_in: true,                session: session            }        else            render json: {                logged_in: false,                error: "Unable to authenticate user."            }        end    end    def destroy        session.clear    end
private def session_params params.require(:user).permit(:email, :password) endend

Problems Controller

  1. In the ProblemsController, we need to include the ActionController::HttpAuthentication::Token module.
  2. Now we can use before_action to authenticate the user before logging them in and creating a new session.
class ProblemsController < ApplicationController    include ActionController::HttpAuthentication::Token    before_action :authenticate_user, only: [:create]    def index        render json: ProblemSerializer.new(Problem.all)    end    def create        problem = Problem.create(problem_params)        if problem.save            render json: ProblemSerializer.new(problem)        else            render json: {error: "Problem did not save."}        end    end    def show        problem = Problem.find(params[:id])        if problem            render json: ProblemSerializer.new(problem)        end    end    private
def authenticate_user token, _options = token_and_options(request) user_id = SessionsController.decode(token) User.find(user_id) rescue ActiveRecord::RecordNotFound render json: {authorized: false} end def problem_params params.require(:problem).permit(:title, :description, :user_id) endend

Frontend

Problem.js

  1. In the frontend, you can authenticate the user each time a fetch request is sent in using the “Authorization”: `Bearer ${sessionStorage.token}` header. Below, I’m authenticating a user before they are able to create a new Problem.
const obj = {    method: "POST",    credentials: "same-origin",    headers: {        "Content-Type": "application/json",        "Accept": "application/json",        "Authorization": `Bearer ${sessionStorage.token}`    },    body: JSON.stringify({problem: {title: title, description: description, user_id: userId}})}fetch("http://localhost:3000/problems", obj)    .then(response => response.json())    .then(jsObj => {        let newProblem = new Problem(jsObj.data)        newProblem.renderProblem()    }).catch(error => {    console.log(error)    alert(error)})

--

--

--

Laotian-American woman pursuing my passion for mentally stimulating and complex problem-solving through programming.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Chindalath Traymany

Chindalath Traymany

Laotian-American woman pursuing my passion for mentally stimulating and complex problem-solving through programming.

More from Medium

How to tweet using Twitter API and NodeJS👋

Google App Engine

Zodeak Opens Up XMAS 30% Discount on DeFi platforms

Shopify Amazon Integration App — Inventory Sync Solution