Blog

Rails – Authentication with JWT token and custom membership (draft)

Source Code References:

https://github.com/rajeshpillai/rails-membership-api

Let’s create a new rails project (in case you don’t have one)

$ rails new simple_membership_api  # use your project name here

Then don’t forget to ‘cd into the project’.

Gemfile

First let’s setup the Gemfile with bcrypt, jwt and rack-cors.

gem 'bcrypt', '~> 3.1.7'   # Used for password digest
gem 'jwt'  # token auth
gem 'rack-cors', '~> 0.4.0'  # cross origin request

After this install the gems by running

$ bundle install

Generator

Create a User model (Note: You can add more fields as needed)

$ rails g scaffold User email:uniq password:digest
# If you dont want the html form, if you are using this as an API project use
# rails g model User email:uniq password:digest

$ rails db:migrate

Adding the digest to the model enables the Rails generator to create a password_digest field in the table and asks for an additional password_confirmation in the form.

The model should contain  has_secure_password which takes care of encrypting the password and provides theauthenticate method to authenticate with that password.

User.rb -> has_secure_password

class User < ApplicationRecord
  has_secure_password
  validates :email, presence: true, uniqueness: true

  def generate_jwt 
    JWT.encode({
      id: id,  # user_id
      exp: 60.days.from_now.to_i},
      Rails.application.secrets.secret_key_base)
  end
end

Sessions Controller

Let’s create a sessions controller for user to log in/ log out. We add an empty new, create and destroy action to our controller.

$ rails g controller sessions new create destroy

Modify the controller code as shown (sessions_controller.rb)

class SessionsController < ApplicationController
  def new
  end
  def create
    user = User.find_by_email(params[:email])
    if user && user.authenticate(params[:password])
      render json: user.as_json(only: [:email])
                      .merge("token": user.generate_jwt)
    else
      render json: { errors: {'email or password': ["is invalid"]}}, 
                    status: :unprocessable_entity
    end
  end
  def destroy
    # If token is saved in DB, you can destroy it here
     head(:ok, status: :no_content) 
  end
end

User Controller

Let’s take a look at the show method of the users controller (which should be used for displaying profile information). In case you don’t have it, you can create it.

# GET /users/1#
# GET /users/1.json
def show
end

If the request type is for json then by default the jbuilder view will be rendered. You can also return json explicitly if you wish.

views/users/show.json.jbuilder

json.partial! "users/user", user: @user

views/users/_user.json.jbuilder (add remove fields here)

json.extract! user, :id, :full_name, :email, :phone, :country, :date_of_birth, :created_at, :updated_at
json.url user_url(user, format: :json)

Routes

Let’s also cross check our routes file.

Rails.application.routes.draw do
  root 'home#index'
  
  resources :users
  resources :sessions, only: [:new, :create, :destroy]
  delete 'logout', to: 'sessions#destroy', as: 'logout'

  # Add the below routes if the login/register page is created in rails
  # If rails is used only as an API backend, these forms can be created
  # on the respective frontend, javascript/react/angular etc.

  # get 'signup', to: 'users#new', as: 'signup'
  # get 'login', to: 'sessions#new', as: 'login'

end

current_user

Let’s create a current_user helper method, which allows access to the currently logged in user.

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  helper_method :current_user

  protect_from_forgery unless: -> { request.format.json?}
  before_action :validate_user!, except: [:login]

   private
  def validate_user! 
    if request.headers['Authorization'].present?
      # {'Authorization' : 'Bearer <TOKEN>'}
      token = request.headers["Authorization"]
      token = token.split(" ")[1]  # Remove "Bearer"

      begin 
        jwt_payload = JWT.decode(token, Rails.application.secrets.secret_key_base).first
        @current_user_id = jwt_payload['id']

      rescue => exception 
        head :unauthorized
      end
    else 
      head :unauthorized
    end
  end
  def current_user
    if session[:user_id]
      @current_user ||= User.find(session[:user_id])
    else
      @current_user = nil
    end
  end
end

To test this start up your rails server

$ rails s

Testing API

Use postman or any other rest-client tool to test the api.

POST /sessions (login)

On success you should get a token

DELETE /logout

You have to pass the token for signout

POST /users.json

This is a quick draft with no error handling. Watch out for updates!

How useful was this post?

Click on a heart to rate it!

Average rating 5 / 5. Vote count: 1

No votes so far! Be the first to rate this post.

Leave a Reply