Skip to content

Instantly share code, notes, and snippets.

@NicholasJacques
Last active June 1, 2023 03:55
Show Gist options
  • Save NicholasJacques/1ee6b20ffd103627cbfce2c07ddf1892 to your computer and use it in GitHub Desktop.
Save NicholasJacques/1ee6b20ffd103627cbfce2c07ddf1892 to your computer and use it in GitHub Desktop.
JWT authentication in rails using jwt gem and bcrypt

Handroll JWT Authentication for your Rails API because no on likes using Devise

The Problem:

If you’re new to building API’s with Rails you’ve probably wondered how to authenticate requests made to the API to ensure that they are coming from the correct source with correct permissions. Since API’s are stateless applications they do not have the ability to create sessions for users. (Read about how Rails Sessions work here) This creates some challenges when trying to handle authentication because the app isn’t able to remember a user’s session data from one request to the next.

The Solution:

Enter Json Web Tokens. Json Web Tokens (JWT) are a self contained authentication method designed for stateless authentication. A self contained authentication method is one that does not require any storage on the back-end to verify the authenticity of the request. All of the data necessary to authenticate the request is contained right inside the token!

What does a JWT look like?

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

That is a JSON web token. If you look closely you will notice that it is punctated by two periods, breaking it up into three sections: Header, Payload, and Signature. The first section, the header, contains information about the hashing algorithm use to encode encode the token and the token’s type. The second section, the payload, contains the token’s metadata, referred to as ‘claims.’ Claims can contain any information that you wish althought for best performance it is best to keep them succinct. Some typical claims include the token’s issuer, its expiration date and the user’s account type. The final section, the signature, is used to verify the authenticity of the token and it’s contents. It contains the token’s header and payload hashed with the algorithm specified in the header and your applications unique secret key.

Let's Implement It:

There are a numerous ways to implement JWT authentication in your API including a couple of Gems that abtract away most of the process. For the sake of understanding, we are going to handroll JWT in this tutorial. Let’s start by adding a user model to your API:

rails g model User

Add the following columns to the users tables:

create_table :users do |t|
  t.string :email,           null: false
  t.string :password_digest, null: false
  t.timestamps
end

Run rake db:migrate to migrate your new users table.

You don’t have to, but it would be a good idea to add some validations to the email attribute in your user model:

validates :email, presence: true,
                  uniqueness: { case_sensitive: false },
                  format: /@/

Uncomment or add bcrypt to your gem file.

In the user model add:

class User < ApplicationRecord
  has_secure_password
  ...

Now we need to add an endpoint to create new users:

rails g controller Users

In UsersController.rb make a create actions:

class UsersController < ApplicationController
  def create
    user = User.new(user_params)
    if user.save
      render json: { message: 'User created successfully' },
             status: :created
    else
      render json: { errors: user.errors.full_messages },
             status: :bad_request
    end
  end
  private
  def user_params
    params.require(:user)
          .permit(:email,
                  :password,
                  :password_confirmation)
  end
end

Now lets build an endpoint for authenticating a user:

rails g controller authentication_controller

In authentication_controller.rb:

class AuthenticationController < ApplicationController
  def create
   user = User.find_by(email: params[:email])
  if user && user.authenticate(params[:password])
    auth_token = JsonWebToken.encode({user_id: user.id})
    render son: {auth_token: auth_token}, status: :ok
  else
    render json: {error: 'Login Unsuccessfull'}, status: :unauthorized
  end
end

You’ll notice a couple of methods in that action that you likely haven’t seen before. That’s because we are going to build them right now.

In your gemfile add gem jwt and then bundle install.

Then in your lib directory lets build a JsonWebToken class to handle creating and decoding web tokens.

class JsonWebToken
  def self.encode(payload)
    payload.reverse_merge!(meta)
    JWT.encode(payload, Rails.application.secrets.secret_key_base)
  end

  def self.decode(token)
    JWT.decode(token, Rails.application.secrets.secret_key_base)
  end

  def self.valid_payload(payload)
    if expired(payload) || payload['iss'] != meta[:iss] || payload['aud'] != meta[:aud]
      return false
    else
      return true
    end
  end

  def self.meta
    {
      exp: 7.days.from_now.to_i,
      iss: 'issuer_name',
      aud: 'client',
    }
  end

  def self.expired(payload)
    Time.at(payload['exp']) < Time.now
  end
end

Finally we need to add some methods to our application controller to use our Json Web Tokens for authentication.

In application_controller.rb:

protected
def authenticate_request!
  if !payload || !JsonWebToken.valid_payload(payload.first)
    return invalid_authentication
  end

  load_current_user!
  invalid_authentication unless @current_user
end

def invalid_authentication
  render json: {error: 'Invalid Request'}, status: :unauthorized
end

private
def payload
  auth_header = request.headers['Authorization']
  token = auth_header.split(' ').last
  JsonWebToken.decode(token)
rescue
  nil
end

def load_current_user!
  @current_user = User.find_by(id: payload[0]['user_id'])
end

With this set up you are able to use ‘authenticate_request’ as a before_action for any endpoints that you want to be protected by authentication.

More Resources:

https://www.sitepoint.com/authenticate-your-rails-api-with-jwt-from-scratch/ https://github.com/jwt/ruby-jwt https://github.com/nsarno/knock

@ritaritual
Copy link

Seems like typo in render son: {...
son instead of json
Cool gist, thank you! I saved few hours for me ❤️

@Wokoro
Copy link

Wokoro commented Sep 8, 2018

Thanks for the wonderful article.

@slindsey3000
Copy link

This is great. Had to use Rails.application.secret_key_base. Put the JsonWebToken class in Models too.

@DevRor1
Copy link

DevRor1 commented Mar 22, 2019

is it possible to use the device gem after integration jwt in rails 5 app? If yes, please share me the process how will i use jwt and device simultaneously without using device-jwt gem, any help would be appreciate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment