Prasanna Natarajan

Rails API: Using Json Web Token for Authentication

GoRails has a great video series explaining JWT and how it can be used to build authentication. I implemented it in a test app and here are the notes from the experience.

The basic idea for the api authentication is this:

First install the jwt gem.

We’ll need a place to encode the payload as JWT, and decode the payload from the JWT. This is the app/models/json_web_token.rb file:

class JsonWebToken

  def self.encode(payload, expiration=expiration_from_config)
    payload = payload.merge(exp: expiration)
    JWT.encode(payload, ENV['SECRET_KEY_BASE'])
  end


  def self.decode(token)
    JWT.decode(token, ENV['SECRET_KEY_BASE'])&.first
  end


  def self.expiration_from_config
    Chronic
      .parse( "#{ENV['JWT_TOKEN_EXPIRATION_MINS']} mins from now")
      .to_i
  end

end

I’ve begin to use the Chronic gem in place of the rails default date helpers. They feel better.

The payload is a hash that holds the currently signed in user’s id in the sub key:

{ sub: user.id }

The login request is served by a authentication_controller.rb:

class AuthenticateController < ApplicationController

  skip_before_action :authenticate_token!, only: [:login]


  def login
    user = User.find_by(email: login_params[:email])

    if user&.authenticate(login_params[:password])
      render json: { token: JsonWebToken.encode(sub: user.id) }
    else
      render json: { errors: [ {title: "email or password is incorrect"} ] }, status: :unauthorized
    end
  end


  private def login_params
    @_login_params ||= params.require(:auth).permit(
      :email,
      :password
    )
  end

end

All the other api requests will be authenticated using a before_action like so:

  before_action def authenticate_token!
    payload = JsonWebToken.decode(auth_token)
    @current_user = User.find_by(id: payload['sub'])
  # https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/error.rb
  rescue JWT::ExpiredSignature
    render json: { errors: [ {title: "Auth token has expired"} ] }, status: :unauthorized
  rescue JWT::DecodeError
    render json: { errors: [ {title: "Invalid auth token"} ] }, status: :unauthorized
  end


  private def auth_token
    @_auth_token ||= request.headers.fetch('Authorization', '').split(' ').last
  end