Authentication with JWT in Rails API

Nurudeen Ibrahim
4 min readMar 8, 2018

--

Authentication is one of the vital parts of any web application, and there are many libraries that provide various options to perform authentication in one way or another. The most prominent gem for authentication in Ruby on Rails is devise which is considered to be an overkill for API-based systems.

For the fact that JWT(JSON web token) gives me total control over my whole authentication process, I prefer using it when it comes to building APIs. In this tutorial, we are going to be talking about how JWT can be used for authenticating Rails API, and at the same time touching a bit of authorization to make it more explanatory and interesting.

Note: This tutorial focuses only on authentication in Rails and not how API is built in Rails. If you are looking for an extensive tutorial on Rails API, kindly check this out https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-one. Now let’s get started.

  1. Add jwt and dotenv-rails to your Gemfile:
gem 'jwt'
gem 'dotenv-rails'

jwt is the main gem that handles tokens encoding and decoding. dotenv-rails will help us keep our jwt secret key as an environmental variable.

Then run bundle install

2. Create a .env file in the root directory and add your jwt secret key:

JWT_SECRET='yoursecretkey'

3. We will create our JsonWebToken class which will contain the logic to encode and decode tokens. So create a jwt folder inside your app folder and add a json_web_token.rb file.

app/jwt/json_web_token.rb:

class JsonWebToken
JWT_SECRET = ENV["JWT_SECRET"]
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, JWT_SECRET)
end
def self.decode(token)
body = JWT.decode(token, JWT_SECRET)[0]
HashWithIndifferentAccess.new body
rescue JWT::ExpiredSignature, JWT::VerificationError => e
raise ExceptionHandler::ExpiredSignature, e.message
rescue JWT::DecodeError, JWT::VerificationError => e
raise ExceptionHandler::DecodeError, e.message
end
end

We are declaring the constant JWT_SECRET in the first line of the class which had already been defined in the .env file. This class contains essentially two class methods, encode and decode.

The encode method accepts two parameters, a payload which is a hash containing key-value you are encoding with, and the token expiry time which we’ve set to a default value of 24hrs. This method relies on the encode method from the JWT class.

The decode method accepts token as the only parameter, and also relies on the provided decode method from JWT class. HashWithIndifferentAccess gives permission to refer to the keys in the decoded body as both symbol and string. We are also raising ExpiredSignature and DecodeError exceptions which are going to be rescued soon to display appropriate custom messages.

4. Let’s create an exception class where we will rescue and provide custom messages for the JWT exceptions we’ve raised.

app/controllers/concerns/exception_handler.rb:

module ExceptionHandler
extend ActiveSupport::Concern
class DecodeError < StandardError; end
class ExpiredSignature < StandardError; end
included do
rescue_from ExceptionHandler::DecodeError do |_error|
render json: {
message: "Access denied!. Invalid token supplied."
}, status: :unauthorized
end
rescue_from ExceptionHandler::ExpiredSignature do |_error|
render json: {
message: "Access denied!. Token has expired."
}, status: :unauthorized
end
end
end

The included method which takes a block of code makes our rescue logic available as soon as this module is included into any class. We then created the exception classes, inheriting from StandardError. Each of the exception classes are rescued by responding with appropriate custom messages.

5. Let’s include the ExceptionClass to the application_controller so that it can be made visible to all our controllers.

app/controllers/application_controller.rb:

class ApplicationController < ActionController::API
include ExceptionHandler
end

6. Now it’s time to create our authentication and authorization files.

app/auth/authentication.rb:

class Authentication
def initialize(user_object)
@username = user_object[:username]
@password = user_object[:password]
@user = User.find_by(username: @username)
end
def authenticate
@user && @user.authenticate(@password)
end
def generate_token
JsonWebToken.encode(user_id: @user.id)
end
end

Our Authentication constructor accepts user_object as a parameter, this is a hash containing both the username and password of the user. The authenticate method simply checks if the user is valid by comparing the username with the hashed password. The generate_token calls the encode method of our JsonWebToken class, and supplies a hash of the user’s id.

app/auth/authorization.rb:

class Authorization
def initialize(request)
@token = request.headers[:HTTP_TOKEN]
end
def current_user
JsonWebToken.decode(@token)[:user_id] if @token
end
end

Our Authorization constructor accepts the request object as a parameter, and from there, extracts the supplied token. The current_user method decodes and gets user_id from the decoded token.

7. The Authentication and Authorization classes are now available for use in any of our controllers. Firstly, let’s see how we can use the Authentication class to control login action inside the users_controller.

app/controllers/users_controller.rb:

class UsersController < ApplicationController  def login
auth_object = Authentication.new(login_params)
if auth_object.authenticate
render json: {
message: "Login successful!", token: auth_object.generate_token }, status: :ok
else
render json: {
message: "Incorrect username/password combination"}, status: :unauthorized
end
end
private def login_params
params.permit(:username, :password)
end
end

The private method login_params returns a hash containing both the username and password from the request body. This a very common way of making sure that the data coming from the request body is secured.

The Authentication class is then instantiated, passing the login_params. The authenticate method is called to ensure validity of the user after which an appropriate message is sent. If the user is valid, the generate_token method is called so as to include token in the response message.

To demonstrate an authorization example, let’s assume there’s a Group model in this application and there is a feature for posting messages to groups whereby only the creator of a group is permitted to post messages.

app/controllers/groups_controller.rb:

class GroupsController < ApplicationController  def post_message
authorization_object = Authorization.new(request)
current_user = authorization_object.current_user
if current_user == Group.find(params[:id]).created_by
# post message
else
# respond: You are not allowed to post to this group
end
end
end

I believe this covers the basics of how JWT can be integrated in Rails. In the second part of this tutorial, I will be explaining how JWT based requests can be tested with rspec.

If you have any suggestion/comment /question as regards this tutorial, kindly post as comment below.

--

--