Rails 6 API fast_jsonapi gem with Devise and JWT authentication

rails 6 api

This article is all about authentication in rails 6 using devise and devise-jwt with fast_jsonapi response.

Fast_jsonapi

A lightning fast JSON:API serializer for Ruby Objects. It is better in performance compared to Active Model Serializer.

Devise and JWT

Devise-jwt is a devise extension which uses JSON Web Tokens(JWT) for user authentication. With JSON Web Tokens (JWT), rather than using a cookie, a token is added to the request headers themselves (rather than stored/retrieved as a cookie). This isn’t performed automatically by the browser (as with cookies), but typically will be handled by a front-end framework as part of an AJAX call.

  1. Create a new Rails API app

    In this step, We need to create a rails application with api_only mode with optional database params(If you want to change).

    $ rails new test-app –api –database=postgresql

    Here, I have created a rails 6 application using postgresql (Default SQLite).
    (Note: If you are using postgresql then you have to setup database.yml)

  2. Configure Rack Middleware

    As this is an API Only application, we have to handle ajax requests. So for that, we have to Rack Middleware for handling Cross-Origin Resource Sharing (CORS)

    To do that, Just uncomment the “gem ‘rack-cors’” line from your generated Gemfile. And add the following lines to application.rb.

    EDITOR

    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins '*'
        resource(
          '*',
          headers: :any,
          expose: ["Authorization"],
          methods: [:get, :patch, :put, :delete, :post, :options, :show]
        )
      end
    end

    Here, we can see that there should be an “Authorization” header exposed which will be used to dispatch and receive JWT tokens in Auth headers.

  3. Add the needed Gems

    Here, we are going to add gem like ‘devise’ and ‘devise-jwt’ for authentication and the dispatch and revocation of JWT tokens and ‘fast_jsonapi’ gem for json response.

    gem 'devise'
    gem 'devise-jwt'
    gem 'fast_jsonapi'
    

    Then, do ‘bundle install’

  4. Configure devise

    By running the following command to run a generator

    $ rails generate devise:install

    It is important to set our navigational formats to empty in the generated devise.rb by adding the following line since it’s an api only app.

    config.navigational_formats = []

    Also, add the following line to config/environments/development.rb

    config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  5. Create User model

    You can create a devise model to represent a user. It can be named as anything. So, I’m gonna be going ahead with User. Run the following command to create User model.

    $ rails generate devise User

    Then run migrations using,

    $ rake db:setup

    or by,

    $ rake db:create
    $ rake db:migrate
  6. Create devise controllers and routes

    We need to create two controllers (sessions, registrations) to handle sign ups and sign ins. By,

    rails g devise:controllers users -c sessions registrations

    specify that they will be responding to JSON requests. The files will looks like,

    class Users::SessionsController < Devise::SessionsController
      respond_to :json
    end
    class Users::RegistrationsController < Devise::SessionsController
      respond_to :json
    end

    Then, add the routes aliases to override default routes provided by devise in the routes.rb

    Rails.application.routes.draw do
      devise_for :users, path: '', path_names: {
        sign_in: 'login',
        sign_out: 'logout',
        registration: 'signup'
      },
      controllers: {
        sessions: 'users/sessions',
        registrations: 'users/registrations'
      }
    end
  7. Configure devise-jwt

    Create a rake secret by running the following command.

    $ bundle exec rake secret

    Add the following lines to devise.rb

    config.jwt do |jwt|
      jwt.secret = GENERATED_SECRET_KEY
      jwt.dispatch_requests = [
        ['POST', %r{^/login$}]
      ]
      jwt.revocation_requests = [
        ['DELETE', %r{^/logout$}]
      ]
      jwt.expiration_time = 30.minutes.to_i
    end

    Here, we are just specifying that on every post request to login call, append JWT token to Authorization header as “Bearer” + token when there’s a successful response sent back and on a delete call to logout endpoint, the token should be revoked.

    The jwt.expiration_time sets the expiration time for the generated token. In this example, it’s 30 minutes.

  8. Set up a revocation strategy

    Revocation of token is conflicting with the main purpose of JWT token. Still devise-jwt comes with three revocation strategies out of the box. Some of them are implementations of what is discussed in the blog post JWT Revocation Strategies

    Here, for the revocation of tokens, we will be using one of the 3 strategies.

    Create a jwt_blacklist model by the following command

    $ rails g model jwt_blacklist jti:string:index exp:datetime

    Add these two lines to the “jwt_blacklist.rb

    include Devise::JWT::RevocationStrategies::Blacklist
    self.table_name = 'jwt_blacklists'

    Add these two options to your devise User model to specify that the model will be jwt authenticatable and will be using the blacklist model we just created for revocation.

    :jwt_authenticatable, jwt_revocation_strategy: JwtBlacklist

    The final user model will look like this

    class User < ApplicationRecord
      devise :database_authenticatable, :registerable,
      :recoverable, :rememberable, :validatable,
      :jwt_authenticatable, jwt_revocation_strategy: JwtBlacklist
    end

    Now run migrations using “rails db:migrate”

  9. Add respond_with using fast_jsonapi method

    As we already added a fast_jsonapi gem. For json response for user data, we have to create a user serializer. By following command,

    $ rails generate serializer user

    It will create a serializer with predefined structure.Now, we have to add the attributes which we have to set as a user response. So I have added user’s id, email and created_at.So the final version of user_serializer.rb

    class UserSerializer
      include FastJsonapi::ObjectSerializer
      attributes :id, :email, :created_at
    end

    We can access serializer data for single record by,

    UserSerializer.new(resource).serializable_hash[:data][:attributes]
    And multiple records by,
    UserSerializer.new(resource).serializable_hash[:data].map{|data| data[:attributes]}

    Now, we have to tell devise to communicate through JSON by adding these methods in the RegistrationsController and SessionsController

    class Users::RegistrationsController < Devise::SessionsController
      respond_to :json
      private
    
      def respond_with(resource, _opts = { + })
        render json: {
          status: {code: 200, message: 'Logged in successfully.'},
          data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
        }
      end
    end
    class Users::SessionsController < Devise::SessionsController
      respond_to :json
      private
    
      def respond_with(resource, _opts = { + })
        render json: {
          status: {code: 200, message: 'Logged in successfully.'},
          data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
        }
      end
    
      def respond_to_on_destroy
        head :ok
      end
    end

    You can modify the column name and data format by overwrite attribute:

    attribute :created_date do |user|
    user.created_at.strftime(‘%d/%m/%Y’)
    end

    Here, I have changed created_at attribute’s column name and its format.

    Here you can get detailed information on fast_jsonapi.

  10. Finally, it’s done

    Now you can add the following line in any controller to authenticate your user.

    before_action :authenticate_user!

    If you are looking to develop any project on Ruby on Rails then choose us as we are one of the leading Ruby on Rails Development Company that provides quality Ruby on Rails development services. Contact us to hire Ruby on Rails developers for your Ruby on Rails requirement or you can reach us at info@techcompose.com