Rails | React

Secure Your Rails and React App with JWT and Refresh Tokens — Part 1

Revolutionize Your Authentication with JWT and Refresh Tokens in Rails and React

Katomaran
4 min readFeb 26, 2023

--

JSON Web Token (JWT) is a popular authentication method in web applications. JWT provides a way to transmit information between services as a JSON object securely. It is often used to authenticate users and provide access to protected resources.

In this blog post, we will explore implementing JWT authentication with a refresh token in a Rails backend and a React frontend application. We will split this article into 3 parts,

  • In the first part, we will discuss how to generate and verify access tokens on the backend.
  • In the second part, we’ll implement refresh tokens for long-term authentication on the backend.
  • In the third part, we’ll explore the implementation of the frontend React application.

How JWTs Work

JWTs consists of three parts: a header, a payload, and a signature. These parts are concatenated with a period (.) character.

Eg: xxxxx.yyyyy.zzzzz

You can find more details about JWT here

Implement Access Token

With the introduction out of the way, let's jump in and write some code.

  • Add the following gems to your Gemfile
...
gem 'jwt'
gem 'dotenv-rails'
...
  • Run bundle install
  • Let's add our secret keys in the .env file
JWT_ACCESS_SECRET=this-is-access-secret
  • First, we can create our helper class at app/helpers/jwt_helper.rb
## app/helpers/jwt_helper.rb

class JwtHelper
def self.sign_access_token(payload)
JWT.encode(
{
data: payload,
exp: exp_short,
iss: 'auth-server', ## Needs to be your signing application
aud: 'api-server', ## Needs to be your recipient application
iat: iat,
},
access_secret,
)
end

def self.verify_access_token(token:, aud: 'api-server', iss: 'auth-server', validate: true)
JWT.decode(
token, access_secret, validate,
{
iss: iss,
aud: aud,
verify_aud: true,
verify_iss: false,
}
).dig(0, 'data')
end

def self.access_secret
ENV.fetch('JWT_ACCESS_SECRET')
end

def self.iat
Time.now.to_i
end

def self.exp_short
Time.now.to_i + 15.minute.to_i
end

private_class_method :iat, :exp_short, :access_secret

end

Now let's get the models and controllers added

## app/models/user.rb

class User < ApplicationRecord
before_create do
self.password = Digest::SHA1.hexdigest(password)
end

def access_token
payload = {
id: id,
email: email
}
JwtHelper.sign_access_token(payload)
end
end
## app/controllers/application_controller.rb

class ApplicationController < ActionController::API
before_action :authentication

def authentication
auth_header = request.headers['Authorization'].presence || (return unauthorized)
_str, token = auth_header.split(' ')
data = JwtHelper.verify_access_token(token: token)
@current_user = User.find(data['id'])
rescue => _e
unauthorized
end

def unauthorized
render json: { message: 'Unauthorized' }, status: :unauthorized
end

attr_reader :current_user
end
## app/controllers/authentication_controller.rb

class AuthenticationController < ApplicationController
skip_before_action :authentication, only: %i[signup login]

def signup
user = User.find_by(email: params[:email])
if user
render json: { message: 'Email already registered' }
return
end

user = User.create!(email: params[:email], name: params[:name], password: params[:password])

render json: {
message: 'User registration successful',
data: user.as_json(only: %i[id email name], methods: %i[access_token])
}, status: :created
end

def login
user = User.find_by(email: params[:email], password: Digest::SHA1.hexdigest(params[:password]))
unless user
render json: { message: 'Invalid email or password' }, status: :unauthorized
return
end

user.update!(last_login_at: Time.now)
render json: {
message: 'User login successful',
data: user.as_json(only: %i[id email name], methods: %i[access_token])
}
end

def protected_route
render json: {
message: "Hello #{current_user.name}, we have implemented JWT authentication"
}
end
end

Finally, let's plug in our routes file

## config/routes.rb

Rails.application.routes.draw do
post :signup, to: 'authentication#signup'
post :login, to: 'authentication#login'
get :protected_route, to: 'authentication#protected_route'
end

That’s it. Our coding part is done and dusted. We can now verify if our APIs are working as expected. First the signup API.

1.1 Signup Request

That was easy. Now let’s try the login

1.2 Login Request

Our login also worked. Now let's try accessing some protected routes and see if we can authenticate the user via the access token

1.3 A protected route Request

Good Job! 🎉🎉That wraps up our JWT-based access token implementation. Now, the access we generated should expire every 15 mins and make us log in every time. This will hamper the experience of your users. We’ll discuss about how to handle this exact problem with refresh token implementation in the next part.

Download the complete Source Code at:

Have questions about building a Rails application? Click here to speak to one of our experts.

Katomaran is a multifaceted product development firm helping enterprises around the world to adapt and thrive in the digital space. We develop best-in-class products that transform your business.

🔔 Connect with us on LinkedIn, Instagram, and Facebook.

--

--