JWT Token-based custom user authentication for Rails API only (Part 02)
Tags: rails, api, authentication, jwt
Published: 2021-05-28 08:16:04

The code available at:
{% github sulmanweb/rails-api-user-custom-auth no-readme %}

> This part emphasis is on user sign up services and if signup is ok create a session with JWT token for that user

## Gems to be installed:

- [jbuilder](https://rubygems.org/gems/jbuilder) - json template engine
- [jwt](https://rubygems.org/gems/jwt) - encoding and decoding jwt oauth
- [figaro](https://rubygems.org/gems/figaro) - for environment variables

## JSON Web Token library:

Create a library for encoding and decoding of the token to be sent to client for authentication.

To eager-load all libraries on load in `config/application.rb` enter line:

```ruby
config.eager_load_paths << Rails.root.join('lib')
```

Now create a library in `lib/json_web_token` folder of the project:

```ruby
class JsonWebToken
  require 'jwt'
  SECRET_KEY = ENV['JWT_SECRET']
  JWT_EXPIRY = 1.day

  def self.encode(payload, exp = JWT_EXPIRY.from_now)
    payload[:exp] = exp.to_i
    JWT.encode(payload, SECRET_KEY, 'HS512')
  end

  def self.decode(token)
    decoded = JWT.decode(token, SECRET_KEY, true, {algorithm: 'HS512'})[0]
    res = HashWithIndifferentAccess.new decoded
    if Time.at(res[:exp]) > Time.now
      res
    else
      nil
    end
  rescue
    return nil
  end
end
```

`SECRET_KEY` could be the key on which encryption of the payload will be done. `encode` method will encode the payload and returns the encoded string. `decode` method decodes the given string based on secret key and return data or `nil` if data is un—decodable.

## Session Create Concern:

Now create a concern in controllers directory for session create so that we can call session wherever we want to call it in controllers. So create file `app/controllers/concerns/create_session.rb`:

```ruby
module CreateSession
  extend ActiveSupport::Concern
  require 'json_web_token'

  def jwt_session_create user_id
    user = User.find_by(id: user_id)
    session = user.sessions.build
    if user && session.save
      return JsonWebToken.encode({user_id: user_id, token: session.token})
    else
      return nil
    end
  end
end 
```

**Concerns** are a great way to DRY the code, but some people suggest don’t use and some suggest you use. More on concerns are available at [https://blog.appsignal.com/2020/09/16/rails-concers-to-concern-or-not-to-concern.html](https://blog.appsignal.com/2020/09/16/rails-concers-to-concern-or-not-to-concern.html).

`jwt_session_create` method gets user ID and create a session in sessions table and encoded using our library the token and ID of the user and returns JWT token to us.

## Registrations Controller:

Now we create the path `/auth/sign_up` using registrations controller. Create a new directory `app/controllers/auth` and new file `registrations_controller.rb` in newly created directory with data:

```ruby
class Auth::RegistrationsController < ApplicationController
  include CreateSession

  def create
    @user = User.new(registration_params)

    if @user.save
      @token = jwt_session_create @user.id
      if @token
        @token = "Bearer #{@token}"
        return success_user_created
      else
        return error_token_create
      end
    else
      error_user_save
    end
  end

  protected

  def success_user_created
    response.headers['Authorization'] = "Bearer #{@token}"
    render status: :created, template: "auth/auth"
  end

  def error_token_create
    render status: :unprocessable_entity, json: { errors: [I18n.t('errors.controllers.auth.token_not_created')] }
  end

  def error_user_save
    render status: :unprocessable_entity, json: { errors: @user.errors.full_messages }
  end

  private

  def registration_params
    params.permit(:name, :email, :password)
  end
end 

```

`create` method gets user’s name, email, and password and tries to create user and if user is created then creates a session and return the token in headers and body with user data.

Now sending this token back in headers while request, user can authenticate itself for that request. We work on authentication next.

Meanwhile, add to `config/routes.rb` file to give the controller a path of POST request:

```ruby
namespace :auth do
  post "sign_up", to: "registrations#create"
end
```

Now send a POST request with email, password, and name to [http://localhost:3000/auth/sign\_up](http://localhost:3000/auth/sign_up). The result is shown below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrxauzw1gtwi4lj4ms3t.png)

## An authenticated User can delete itself:

Now we need to create another concern that checks the token from the headers and verifies the token if user is valid or not. So create a file `app/controllers/concerns/authenticate_request.rb` with following data:

```ruby
module AuthenticateRequest
  extend ActiveSupport::Concern
  require 'json_web_token'

  def authenticate_user
    return render status: :unauthorized, json: {errors: [I18n.t('errors.controllers.auth.unauthenticated')]} unless current_user
  end

  def current_user
    @current_user = nil
    if decoded_token
      data = decoded_token
      user = User.find_by(id: data[:user_id])
      session = Session.search(data[:user_id], data[:token])
      if user && session && !session.is_late?
        session.used
        @current_user ||= user
      end
    end
  end

  def decoded_token
    header = request.headers['Authorization']
    header = header.split(' ').last if header
    if header
      begin
        @decoded_token ||= JsonWebToken.decode(header)
      rescue Error => e
        return render json: {errors: [e.message]}, status: :unauthorized
      end
    end
  end
end
```

In the `app/controllers/application_controller.rb` add the following two lines:

```ruby
include AuthenticateRequest
before_action :current_user
```

Now, before every action whether it needs to authenticate or not, `current_user` method will be called. This method checks whether authorisation token present or not. If token present, this method will decode the token and verifies the user and returns the authenticated user to the current user method which can now be accessed in all requests.

Also, now if we enter `authenticate_user` in any controllers before action if valid token is not passed then there will be an error of 401.

Now, we create destroy user method. In registrations controller add line in top:

```ruby
before_action :authenticate_user, only: :destroy
```

and a method:

```ruby
def destroy
  current_user.destroy
  success_user_destroy
end

protected

def success_user_destroy
  render status: :no_content, json: {}
end
```

So now first user will be authenticated based on token and then that user will be destroyed. If user isn’t valid or token is absent then 401 is returned.

Add to routes in auth namespace:

```ruby
delete "destroy", to: "registrations#destroy"
```

So, Now try `delete` operation on [http://localhost:3000/auth/destroy](http://localhost:3000/auth/destroy) with `Authorization` header with returned token.

Results are below:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vaf3g3nm5mgv6ydc2jil.png)

---- 

So, we can now successfully create a user with session in JWT with authentication system. In the next part I will demonstrate sign in and out and confirmation and reset password system in detail.

Happy Coding!