Rails Token Authentication with JWT and BCrypt Gems
One of the most popular ways of authentication use in rails 5 and higher is the token-based authentication, where a generated created when an user logs in and such token is pass with every request the user make to as a proof of identity to authorize access to resources in the server. In this guide I am going to show you how to quickly set up a token-base authentication API in Rails 6 using JWT and BCrypt gems.
Lets start by creating a new project
rails new api-app --api
Them let’s modify the cors.rb file so it can take cors.rb to allow Cross origin resource sharing. More info about rack-cors in ruby https://github.com/cyu/rack-cors
//config/initializers/cors.rb
# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.# Read more: https://github.com/cyu/rack-corsRails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get,:post,:put,:patch,:delete,:options,:head]
end
end
Then in your gem file uncomment rack-cors gem
//Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'
Then in the terminal run
Bundle install
You will notice that the change was just to uncomment a couple of lines of code and change origins to *. This will allow access to all the resources regardless of where the request is coming from.
Now lets enable BCrypt and Add JWT gem. In your gem file let’s uncomment the BCrypt gem.
//Gemfile
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'
Now let’s add the jwt gem by running the following command in your terminal.
Bundle add JWT
Now that we have basic setup, lets create the user model to login
rails g model user username:string password_digest:string
let’s run the new migration in the terminal
rails db:migrate
and in the User model
//app/models/user.rb
class User < ApplicationRecord
has_secure_password
end
let’s create an authentication controller
rails g controller Authentication
Before continuing working in our authentication controller, lets create the some methods that we are going to need in our application_controller.rb
class ApplicationController < ActionController::API
before_action :authorizedef authorize
@headers = request.headers
if @headers['Authorization'].present?
token = @headers['Authorization'].split(' ').last
decoded_token = decode(token)
@user = User.find_by(id:decoded_token[:user_id])
render json:{ error: 'Not Authorized' }, status:401 unless @user
else
render json: { error: 'Not Authorized' }, status: 401
end
end
private
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end def decode(token)
body =
JWT.decode(token,Rails.application.secrets.secret_key_base)[0]
HashWithIndifferentAccess.new body
rescue
nil
end
end
In here we have three methods:
encode
, use to create the token, it takes a payload which is a hash where we are going to be passing the user id, and expiration time that sets the lifespan of the token.decode
, use te decode the token and extract the user id that was previously encode.authorize
, we are going to run before almost every action to access resources. It would take the token and decode it get the user id and validate if such user id exist in the database to allow access to all resources
The before_action
makes sure to run the authorize method before every action of all the controllers that inherit from ApplicationController.
Now we can continue on working in our authentication controller. Inside our authentication method
class AuthenticationController < ApplicationController
skip_before_action :authorize
def authenticate
user = User.find_by(username:params[:username])
if user && user.authenticate(params[:password])
token = encode(user_id: user.id)
render json: {token:token,user:user}
else
render json: {error:"An Error Happened"}
end
end
end
Now that we have all the code,we can test it out. Let’s create an user in the rails console, username: gary1690 and password:12345
Now that our new user is created we can authenticate it. I will be using postman for this. I will send a request to https://localhost:3000/authenticate and send the username and password of the user we previously created. The result is shown below.
Now that we authenticate our user, let’s create son resources to access with our token
rails g reources Items name:string
then run our migration
rails db:migrate
and create our index action in the ItemsController.rb
class ItemsController < ApplicationController
def index
render json: Item.all
end
end
Now we can request our resources using the token
We successfully got the list of items back, you can try creating items to get a full list instead of an empty array.If you do you will get something like this