JWT Auth in a Rails API

In this article we will integrate a JWT authentication from the scratch in a Rails API.

If you are not familiarized with the JWT concept, the official site defines it as:

an open, industry standard RFC 7519 method for representing claims securely between two parties.

Basicamente vamos usar um token para verificar a autenticidade de um usuário para rotas protegidas em nossa API.

We will use a token to verify the authenticity of an user to access protected routes in our API.

You can find more information about JWT in the website.

Going forward, we will create a API Only Rails Project, for that you should run the command below on your terminal:

rails new jwt-auth-example --database=postgresql --api

With our project ready, let's stark working on it.

On your Gemfile, uncomment bcrypt and rack-cors gems. We gonna use them both.

You will also need to add the JWT gem. So add to your Gemfile:

gem 'jwt'

Don't forget to run bundle on your terminal, to install the new gems.

The CORS initializer needs to be allowed on our project, fortunately it already comes pre-settled, all you need to do is uncomment it. You can find it in config/initializers/cors.rb

Uncommenting the content between the lines 8 and 16, it will looks like this:

# 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 'example.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end

Awesome! Now our application allows Cross Origin Resources Sharing, the CORS.

Now let's start working on the routes.

Rails.application.routes.draw do
namespace :api do
namespace :v1 do
post '/signup', to: 'users#signup'
post '/signin', to: 'users#signin'
get '/signedin', to: 'users#signedin'
end
end
end

The /signup route will be used for signup users (obviously 😆). The /signin route will be for signin existent users (obviously again). And finally, the /signedin route will be the one we check if there is a current user session.

With our routes file ready, we will create our User’s model.

rails g model User email:string password_digest:string

The Bcrypt gem we added in the beginning will be responsible for encrypt and ensure the safety of the passwords.

We need to add the Bcrypt’s has_secure_password method in our User's model. We also will include a validation to make sure that we will haven't duplicated emails.

class User < ApplicationRecord
has_secure_password
validates :email, uniqueness: { case_sensitive: false }
end

Now we need to create our database and run the pending migrations, in this case is only the users table.

rails db:create db:migrate

With the last step ready, we will create the Users controller and add the JWT auth methods to the Application Controller, to finally test our application 🥳.

To create the Users Controller

rails g controller Api::V1::Users

The content should looks like:

module Api
module V1
class UsersController < ApplicationController
skip_before_action :set_auth_header, only: %i[signup signin]
before_action :authorize, only: %i[signedin]
def signup
@user = User.create(user_params)
if @user.valid?
token = encode_token({ user_id: @user.id })
render json: { user: @user, token: token }
else
render json: "\"Invalid Credentials\"" , status: :unauthorized
end
end
def signin
@user = User.find_by(email: user_params[:email])
if @user && @user.authenticate(user_params[:password])
token = encode_token({ user_id: @user.id })
render json: { user: @user, token: token }
else
render json: "\"Invalid Credentials\"" , status: :unauthorized
end
end
def signedin
render json: @user
end
private

def user_params
params.require(:user).permit(:email, :password)
end
end
end
end

Still working with controllers, now we should define our auth methods in app/controllers/application_controller.rb:

class ApplicationController < ActionController::API
def encode_token(payload)
JWT.encode(payload, 'Secret Key')
end
def auth_header
request.headers['Authorization'].split(' ')[1]
end
def decoded_token
return unless auth_header.present?
token = auth_header
begin
JWT.decode(token, 'Secret Key', true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
def set_auth_header
response.set_header('Auth-Token', auth_header)
end
def logged_user
return unless decoded_token.present?
id = decoded_token[0]['user_id']
@user = User.find(id)
end
def is_logged?
logged_user
end
def authorize
return if is_logged?
render json: { message: 'You must signin before access this route' }, status: :unauthorized
end
end

Attention: in case you are working on a real project, where we have Secret Key you should replace with a safe key. This key should never be shared in your code, I deeply recommend to add it in the Rails Credentials file or in an ENV file. 😉

With all this stuff done, we are ready to test our authentication! 🥳

Up your local server:

rails s -p 3001

Test in your HTTP client the endpoints below.

Signup

Request: POST

Endpoint:

http://localhost:3001/api/v1/signup

Headers:

Content-Type: application/json

Body:

{
"user": {
"email": "bruno@teste.com",
"password": "123456"
}
}

Signin

Request: POST

Endpoint:

http://localhost:3001/api/v1/signin

Headers:

Content-Type: application/json

Body:

{
"user": {
"email": "bruno@teste.com",
"password": "123456"
}
}

Signedin

Request: GET

Headers:

Authorization: Bearer {{Token retornado na request de signup ou signin}}

We are all done here!!!

The code developed in this post will be available in a public repository on my GitHub: https://github.com/Hey-Feres/JwtAuthExample

Hope this post helped you in some way. 😄

Feel free to place suggestions or questions in the comment section below.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store