The code available at:
sulmanweb / rails-api-user-custom-auth
Rails API user custom authentication using JWT project
This part emphasis is on user sign up services and if signup is ok create a session with JWT token for that user
Gems to be installed:
- jbuilder - json template engine
- jwt - encoding and decoding jwt oauth
- figaro - for environment variables
JSON Web Token library:
Create a library for encoding and decoding of the token to be sent to client for authentication.
To eager-load all libraries on load in config/application.rb
enter line:
config.eager_load_paths << Rails.root.join('lib')
Now create a library in lib/json_web_token
folder of the project:
class JsonWebToken
require 'jwt'
SECRET_KEY = ENV['JWT_SECRET']
JWT_EXPIRY = 1.day
def self.encode(payload, exp = JWT_EXPIRY.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY, 'HS512')
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY, true, {algorithm: 'HS512'})[0]
res = HashWithIndifferentAccess.new decoded
if Time.at(res[:exp]) > Time.now
res
else
nil
end
rescue
return nil
end
end
SECRET_KEY
could be the key on which encryption of the payload will be done. encode
method will encode the payload and returns the encoded string. decode
method decodes the given string based on secret key and return data or nil
if data is un—decodable.
Session Create Concern:
Now create a concern in controllers directory for session create so that we can call session wherever we want to call it in controllers. So create file app/controllers/concerns/create_session.rb
:
module CreateSession
extend ActiveSupport::Concern
require 'json_web_token'
def jwt_session_create user_id
user = User.find_by(id: user_id)
session = user.sessions.build
if user && session.save
return JsonWebToken.encode({user_id: user_id, token: session.token})
else
return nil
end
end
end
Concerns are a great way to DRY the code, but some people suggest don’t use and some suggest you use. More on concerns are available at https://blog.appsignal.com/2020/09/16/rails-concers-to-concern-or-not-to-concern.html.
jwt_session_create
method gets user ID and create a session in sessions table and encoded using our library the token and ID of the user and returns JWT token to us.
Registrations Controller:
Now we create the path /auth/sign_up
using registrations controller. Create a new directory app/controllers/auth
and new file registrations_controller.rb
in newly created directory with data:
class Auth::RegistrationsController < ApplicationController
include CreateSession
def create
@user = User.new(registration_params)
if @user.save
@token = jwt_session_create @user.id
if @token
@token = "Bearer #{@token}"
return success_user_created
else
return error_token_create
end
else
error_user_save
end
end
protected
def success_user_created
response.headers['Authorization'] = "Bearer #{@token}"
render status: :created, template: "auth/auth"
end
def error_token_create
render status: :unprocessable_entity, json: { errors: [I18n.t('errors.controllers.auth.token_not_created')] }
end
def error_user_save
render status: :unprocessable_entity, json: { errors: @user.errors.full_messages }
end
private
def registration_params
params.permit(:name, :email, :password)
end
end
create
method gets user’s name, email, and password and tries to create user and if user is created then creates a session and return the token in headers and body with user data.
Now sending this token back in headers while request, user can authenticate itself for that request. We work on authentication next.
Meanwhile, add to config/routes.rb
file to give the controller a path of POST request:
namespace :auth do
post "sign_up", to: "registrations#create"
end
Now send a POST request with email, password, and name to http://localhost:3000/auth/sign_up. The result is shown below:
An authenticated User can delete itself:
Now we need to create another concern that checks the token from the headers and verifies the token if user is valid or not. So create a file app/controllers/concerns/authenticate_request.rb
with following data:
module AuthenticateRequest
extend ActiveSupport::Concern
require 'json_web_token'
def authenticate_user
return render status: :unauthorized, json: {errors: [I18n.t('errors.controllers.auth.unauthenticated')]} unless current_user
end
def current_user
@current_user = nil
if decoded_token
data = decoded_token
user = User.find_by(id: data[:user_id])
session = Session.search(data[:user_id], data[:token])
if user && session && !session.is_late?
session.used
@current_user ||= user
end
end
end
def decoded_token
header = request.headers['Authorization']
header = header.split(' ').last if header
if header
begin
@decoded_token ||= JsonWebToken.decode(header)
rescue Error => e
return render json: {errors: [e.message]}, status: :unauthorized
end
end
end
end
In the app/controllers/application_controller.rb
add the following two lines:
include AuthenticateRequest
before_action :current_user
Now, before every action whether it needs to authenticate or not, current_user
method will be called. This method checks whether authorisation token present or not. If token present, this method will decode the token and verifies the user and returns the authenticated user to the current user method which can now be accessed in all requests.
Also, now if we enter authenticate_user
in any controllers before action if valid token is not passed then there will be an error of 401.
Now, we create destroy user method. In registrations controller add line in top:
before_action :authenticate_user, only: :destroy
and a method:
def destroy
current_user.destroy
success_user_destroy
end
protected
def success_user_destroy
render status: :no_content, json: {}
end
So now first user will be authenticated based on token and then that user will be destroyed. If user isn’t valid or token is absent then 401 is returned.
Add to routes in auth namespace:
delete "destroy", to: "registrations#destroy"
So, Now try delete
operation on http://localhost:3000/auth/destroy with Authorization
header with returned token.
Results are below:
So, we can now successfully create a user with session in JWT with authentication system. In the next part I will demonstrate sign in and out and confirmation and reset password system in detail.
Happy Coding!
Top comments (0)