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
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.
That was easy. Now let’s try the login
Our login also worked. Now let's try accessing some protected routes and see if we can authenticate the user via the access token
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.