Simple approach to Rails 5 API authentication with Json Web Token
Many programmers believe ruby is too easy…, yes I agree. I think ruby makes programmers happy. Rails is written in Ruby, the programming language which is also used alongside Rails. Before we move further, it is better to understand that ruby
is a language
such as php or python while rails
is a framework
such Laravel and Django respectively. Ruby on Rails offers more than just pure speed, it’s faster to develop an initial working product in Rails. One of key principles of Ruby on Rails development is convention over configuration. This means that the programmer does not have to spend a lot of time configuring files in order to get setup, Rails comes with a set of conventions which help speed up development.
Why JSON Web Token ?
With JWTs, you can save it in sessions or cookies without worrying about it being manipulated by the user or another third party, thanks to it being signed by the server and secret key. You also need to be more careful with the kind of data you store with JWTs, it is not proper to store sensitive data in JWTs.
How do JSON Web Tokens work?
During authentication, when the user successfully logs in using their credentials, a JSON Web Token will be returned which could be saved in sessions, localstorage or cookies,whenever the user wants to access a protected route or resource, the user agent(AJAX call or Postman request) should send the JWT with the Authorization
header using the Bearer
schema. The content of the header should look like the following:
Authorization: Bearer <token>
The following diagram shows this process:
What is the JSON Web Token structure?
The token
is separated in three base-64 encoded, dot-separated values, each one representing a different type of data:
- Header
- Payload
- Signature
Header
The header
contains of two parts: the type of the token, which is JWT, and the hashing algorithm being used, such as HMAC SHA256 or RSA.
Payload
The payload
contains user detail such as id
or roles
, remember I said it is advisable not save sensitive data in JWT.
Signature
Signature
is a unique key used to verify the authentic owner of the JWT and also to check if the token has not been manipulated. We can use (Rails.application.secrets.secret_key_base)
(which is provide by rails) to generate a unique and secure key, but you can also supply your own secret key if you want.
Wahooooo !!!, enough of stories. Lets get straight to work. We will create a simple rails API to demonstrate the use of JWT in rails.
System requirements
Before you start anything, you must have all this things installed on your system
- Ruby version > 2.4.0
$ brew install ruby
- Rails version 5
$ gem install bundler
$ gem install rails
Visit Ruby on Rails - Installation for more installation details for windows and Mac users.
So lets get started, to generate our application run the command below in your terminal, ensure you navigate to the directory where you want to keep your project.
$ rails new my_api_app --api -T
Don’t get freaked out with the commands, let me explain what they all mean. rails
as you know by now is the the framework we are using , so we use rails to generate migrations
, models
, controllers
, new project(my_api_app)
, among others. --api
command tells rails that we need just API application. -T
excludes Minitest when its generating the application. Minitest is a default testing framework for rails, this doesn’t mean we are not going to write test, the thing is we don’t want to use rails default test suit, rather we will use RSpec
to test our application. I will explain more about the RSpec
test when we get to that part, for now lets get our app wired up for authentication.
Here are the list of things we will cover.
- Create a model
- Set up controller for signup and logging user to the application
- Encoding and decoding user data with JWT.
- We need to validate user credentials
- We need authorization
Create Model
$ cd my_api_app (`to move into root directory of our application`)
$ rails g model User name email password_digest
This command generates User model for us (g means generate) and also a migration file. The content of migration file is what will help us create a table in the database when run our migration command. it also specifies the fields that we added to our model. You see, I told you rails is easy and it makes you happy, it does all the configuration for you.
Migration
$ rails db:migrate
Running this commands automatically creates our table for us in the database.
We need to ensure our password is not saved in plaintext, in other to achieve this we need to tell our model to encrypt the password before saving into the database. Well, in rails we have a 'gem' package that does that for us.
#Gemfile.rb
gem 'bcrypt', '~> 3.1.7'
$ bundle install
First we add gem
package(bcrypt
) to Gemfile.rb
file. and the command bundle install
installs the package to our project. With the gem installed we can use method has_secure_password
in our model. This method automatically encrypts the password we send into the database from the controller. This is awesome, yes you can say that again.
#app/models/user.rb
class User < ApplicationRecord
#Validations
validates_presence_of :name, :email, :password_digest
validates :email, uniqueness: true
#encrypt password
has_secure_password
end
validates_presence_of
is a rails validation method which ensures that values for the specified fields are provided. Our model is now ready for connection.
Create controller
We need to create user before we can authenticate.
$ rails g controller Users
Implement controller
# app/controllers/users_controller.rb
class UsersController < ApplicationController
# POST /register
def register
@user = User.create(user_params)
if @user.save
response = { message: 'User created successfully'}
render json: response, status: :created
else
render json: @user.errors, status: :bad
end
end
private
def user_params
params.permit(
:name,
:email,
:password
)
end
end
Create routes
#config/routes.rb
Rails.application.routes.draw do
#add our register route
post 'auth/register', to: 'users#register'
end
'auth/register’
is our path (url), ‘user#register’
means we are connecting to user controller, then run register action(method)
In other for our us to connect to and get good response from our application, we need to include some contents in application.rb
file.
#config/application.rb
module MyApiApp
class Application < Rails::Application
# [...]
#cors configuration
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
#autoloads lib folder during production
config.eager_load_paths << Rails.root.join('lib')
#autoloads lib folder during development
config.autoload_paths << Rails.root.join('lib')
# [...]
end
end
#Gemfile.rb
gem 'rack-cors', :require => 'rack/cors'
$ bundle install
rack-cors
gem provides support for Cross-Origin Resource Sharing (CORS) which allows AJAX
calls and postman requests to get good response from our application.
Great work, we are ready for the first test.
Run this command to start the application.
$ rails s
Rails runs on default port 3000
Open your postman so the we can test this implantation
- Select
Post
request - Add the URL
http://localhost:3000/auth/register
- Select x-www-form-urlencoded
- Fill the fields(
key
) and thevalues
- Click Send
You should have something like
To continue, please stop the server with the command below or open a new terminal and navigate to the root directory of the project
$ ctrl + c
Encoding and decoding user data with JWT.
We can start the implementation of JWT generation now. We need to install jwt
gem which makes encoding and decoding of HMACSHA256 tokens available in our Rails application.
#Gemfile.rb
$ gem 'jwt'
$ bundle install
We can make JWT accessible anywhere in our application, to achieve this we can use singleton class
. A singleton class is used for wrapping logic and using it in other constructs I.e You can create a singleton class and defining different logics you need in the class, so that when ever you need any of the method(logic) in our application, you call the class dot
the name of the method.
Let us add json_web_token.rb file in the lib folder, since we have configured our application to auto-loaded it in development and production
$ touch lib/json_web_token.rb
JWT singleton class
# lib/json_web_token.rb
class JsonWebToken
# our secret key to encode our jwt
class << self
def encode(payload, exp = 2.hours.from_now)
# set token expiration time
payload[:exp] = exp.to_i
# this encodes the user data(payload) with our secret key
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def decode(token)
#decodes the token to get user data (payload)
body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
HashWithIndifferentAccess.new body
# raise custom error to be handled by custom handler
rescue JWT::ExpiredSignature, JWT::VerificationError => e
raise ExceptionHandler::ExpiredSignature, e.message
rescue JWT::DecodeError, JWT::VerificationError => e
raise ExceptionHandler::DecodeError, e.message
end
end
end
We have wrapped our JWT in a singleton class to make encoding and decoding method accessible in our application. The encode method generates our token using payload(user data), expiration time and secret key. The decode method takes in the token as parameter and decodes it with same secret key we used to encode it, if this process fails JWT raise exception error which will handle in the next file we will create.
$ touch app/controllers/concerns/exception_handler.rb
module ExceptionHandler
extend ActiveSupport::Concern
# Define custom error subclasses - rescue catches `StandardErrors`
class AuthenticationError < StandardError; end
class MissingToken < StandardError; end
class InvalidToken < StandardError; end
class ExpiredSignature < StandardError; end
class DecodeError < StandardError; end
included do
# Define custom handlers
rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request
rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two
rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two
rescue_from ExceptionHandler::ExpiredSignature, with: :four_ninety_eight
rescue_from ExceptionHandler::DecodeError, with: :four_zero_one
rescue_from ActiveRecord::RecordNotFound do |e|
render json: { message: e.message }, status: :not_found
end
rescue_from ActiveRecord::RecordInvalid do |e|
render json: { message: e.message }, status: :unprocessable_entity
end
end
private
# JSON response with message; Status code 422 - unprocessable entity
def four_twenty_two(e)
render json: { message: e.message }, status: :unprocessable_entity
end
# JSON response with message; Status code 401 - Unauthorized
def four_ninety_eight(e)
render json: { message: e.message }, status: :invalid_token
end
# JSON response with message; Status code 401 - Unauthorized
def four_zero_one(e)
render json: { message: e.message }, status: :invalid_token
end
# JSON response with message; Status code 401 - Unauthorized
def unauthorized_request(e)
render json: { message: e.message }, status: :unauthorized
end
end
This modules handles all exceptions raised in our application. You might be wondering why we put this in the concerns
folder, we put any method that we need in multiple controllers in this folder and extend ActiveSupport::Concern
is a rails mechanism that performs this wonder. Isn’t that amazing.
Validate Credentials
Now that we have user in our database, we can start authentication. We will need another gem called simple_command. Its serves as an helper for our controllers, Instead of using private controller methods and writing too many if conditions, simple_command
helps us to avoid that and simplify our work.
let us install the gem to our project.
#Gemfile.rb
gem 'simple_command'
bundle install
Since we want to create authentication and authorization services I prefer to create a separate folder for them.
# create auth folder to house auth services
$ mkdir app/auth
$ touch app/auth/authenticate_user.rb
# app/auth/authenticate_user.rb
class AuthenticateUser
prepend SimpleCommand
attr_accessor :email, :password
#this is where parameters are taken when the command is called
def initialize(email, password)
@email = email
@password = password
end
#this is where the result gets returned
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
def user
user = User.find_by_email(email)
return user if user && user.authenticate(password)
errors.add :user_authentication, 'Invalid credentials'
nil
end
end
This command takes in user's email
and password
as parameters and initializes a class instance with email and password attributes that are accessible within the class. The private method user
checks if the user exist in the database, this method also validates the password user supplies to ensure it matches the encrypted password in the database. If everything is true
, a token
will be returned. If not, the method will return nil.
User Authorization
Now that we have generated JWT successfully, we can use it to authorize users that are using our application. To authorize users accessing our application, the token created during login must must be sent through the request header. We can attach our token to the 'Authorization' header, this will allow the server to interprete request properly.
Create authorization file
$ touch app/auth/authorize_api_request.rb
Here is the code for authorization
# app/commands/authorize_api_request.rb
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
@headers = headers
end
def call
user
end
private
attr_reader :headers
def user
@user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
@user || errors.add(:token, 'Invalid token') && nil
end
def decoded_auth_token
@decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else errors.add(:token, 'Missing token')
end
nil
end
end
Hmmmmmmmmmm, this looks like crap right, don’t be afraid. lets go through the code together.
The ||=
operator is used to assign @user i.e if there is no value coming in, then use the last value that was saved, so if User.find()
returns an empty set or decoded_auth_token
returns false
, @user
will be nil
. The user
method will either return the user
if authorization is successful or throw an error if otherwise. Method http_auth_header
extracts the token from the authorization header received in the initialization of the class, while the method decoded_auth_token
decodes the token received from http_auth_header
and retrieves the payload(user's id)
We have implemented all the methods need for our JWT. Now we can create methods in the controller to put our authentication and authorization to work. But first lets create helper method in the application controller which all other controllers will inherit from
Here is the code.
# app/controllers/authentication_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
include ExceptionHandler
# [...]
private
def authenticate_request
@current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless @current_user
end
end
before_action
here is ruby hook that ensures to runs a method before any other method that is being called in the application. Here we want to execute authenticate_request
before user access any method in the application, it request for header every time the user makes a request and passes it into AuthorizeApiRequest
to authorize the user. If the user is successfully authorize, it return user data and saves in @current_user which will be available to all other controllers. Don't forget we created ExceptionHandler
module in the concerns folder, we need to include it here in the application controller
in other to make it accessible to other controllers.
lets us create a method to login.
First we need to add the login and a test route to the route.rb file
# config/routes.rb
Rails.application.routes.draw do
# [...]
post 'auth/login', to: 'users#login'
get 'test', to: 'users#test'
# [...]
end
Login code
# app/controllers/users_controller.rb
class UsersController < ApplicationController
skip_before_action :authenticate_request, only: %i[login register]
# [...]
def login
authenticate params[:email], params[:password]
end
def test
render json: {
message: 'You have passed authentication and authorization test'
}
end
# [...]
# [...]
Private
def authenticate(email, password)
command = AuthenticateUser.call(email, password)
if command.success?
render json: {
access_token: command.result,
message: 'Login Successful'
}
else
render json: { error: command.errors }, status: :unauthorized
end
end
# [...]
end
In our last method, you remember I said authenticate_request
runs before any method in the application to authorize users, we do not need authorization for login
and register
methods because you can not authorize user that has not registered at all or a user that wants to login. skip_before_action
helps us to skip the authorization for register
and login
methods.
Wooooolaaaaaa. Nice job so far. Now it is time to fire up our postman again to test our routes
Start the server
rails s
Remember we created an account early with email: omedale@gmail.com
and password: user
, you can go back to create with your own email and password.
- Open your postman
- Select
Post
request - Add the URL
http://localhost:3000/auth/login
- Select x-www-form-urlencoded
- Fill the fields(
key
) and thevalues
- Click Send
Now let us test the protected routes http://localhost:3000/test
- Copy the token careful
- Open a new tab or replace the curent url with
http://localhost:3000/test
- Change request button to
GET
- Select header
- Fill the fields(
key
) =Authorization
and thevalue
=Bearer <token>
- Click send
Awesomeeeeeeeeeeee
Conclusion
We really covered a lot in this post, we will write test, integrate CI and deploy the app to heroku in our next post. I hope this was helpful. Click here for the full source code.
Such an informative blog, We also posted same blog on Rails API Authentication with JWT. if you wants to know more about Rails API Authentication with JWT, then must check the following link of our blog:
https://www.bluebash.co/blog/rails-6-7-api-authentication-with-jwt/
Very good article! I could finally set up my Rails API with user authentication. With this setup what would be the easiest solution to keep the users logged in, i.e. to generate a new token and store it in the localstore again. Unfortunately I could not adapt some researches to exactly this code. I am new to Ruby on Rails. Thanks a lot in advance!
I can’t find
user.authenticate(password)
method anywhere in the source code? How does that work???!??
The “has_secure_password” method gives you, #authenticate method, which you can use to authenticate passwords.