JWT Authentication in Rails APIs — Getting Started with System Integration
Introduction
If you are starting to build APIs with Rails, you probably have heard about JWT — JSON Web Tokens — a modern, simple, and scalable way to authenticate systems and users in RESTful APIs.
In this article, we will understand what JWT is, why to use it for system integrations, and create a simple Rails implementation to authenticate systems using a single token generated internally, linked to an account, allowing access to protected routes.
Nothing complex, just the essentials so you can start using JWT today!
🏗️ Use Case: Imagine your company offers an API for partners or internal systems to consume data or perform operations.
Each partner has an account and receives an access token generated manually, for example, via ActiveAdmin or another internal dashboard.
This token allows the partner to access protected routes on your API.
👉 No login, password, or OAuth! Just a secure, simple, and efficient token.
Complete step-by-step guide to using JWT in Rails
1. Environment setup and installing the JWT gem
To use JWT in Rails, we need the official jwt gem. Add it to your Gemfile:
gem 'jwt'
Then run in your terminal:
bundle install
Explanation: The jwt gem provides functionality to generate, sign, decode, and verify JWT tokens.
2. Configuring the JWT secret and parameters
Create or edit config/initializers/api_config.rb to set the secret, expiration time, and algorithm:
module ApiConfig < ApplicationConfig
attr_config jwt_secret: Rails.env.production? ? ENV["INTEGRATIONS_JWT_SECRET"] : Rails.application.secret_key_base
attr_config jwt_ttl_hours: ENV["INTEGRATIONS_JWT_TTL_HOURS"]&.to_i || 24
attr_config jwt_algorithm: "HS256"
end
Explanation: Centralizes JWT settings for easy maintenance and security.
3. Defining API routes in config/routes.rb
Organize API routes using namespaces and versioning, with an endpoint for authentication:
Rails.application.routes.draw do
namespace :api do
post "/auth", to: "tokens#create"
namespace :v1 do
resources :products, only: [:show]
end
end
end
Explanation:
4. What is JWT?
JWT (JSON Web Token) is an open standard defined by RFC 7519 that describes a compact and self-contained way to securely transmit information between parties as a JSON object.
It is stateless, meaning it does not require server-side storage to validate the token, unlike traditional sessions (stateful).
5. Stateful vs Stateless: why JWT?
Stateful (traditional sessions): The server keeps session state for each authenticated user/system, using cookies or storing data in databases or caches.
Stateless (JWT): The token carries all necessary information and can be validated without checking the database. This improves scalability and reduces memory/cache dependency.
6. Why do APIs use JWT?
7. How does JWT signing work?
JWT consists of three parts: header, payload, and signature.
To ensure the token was not altered, the signature uses cryptographic algorithms.
Common algorithms:
In this article, we use the default HS256 algorithm, the most common for internal APIs and controlled integrations.
8. Token issuance and verification services
Token issuer service
class TokenIssuerService
def initialize(account_id:)
@account_id = account_id
end
def call
payload = {
account_id: @account_id,
exp: ApiConfig::EXPIRATION_HOURS.hours.from_now.to_i
}
JWT.encode(payload, ApiConfig::SECRET, ApiConfig::ALGORITHM)
end
end
Explanation: Receives an account_id and generates a JWT token including expiration time.
Token verifier service
class TokenVerifierService
def initialize(token:)
@token = token
end
def call
decoded_token = JWT.decode(@token, ApiConfig::SECRET, true, { algorithm: ApiConfig::ALGORITHM })
payload = decoded_token.first
raise 'Account not found' unless Account.exists?(payload['account_id'])
payload
rescue JWT::ExpiredSignature
raise 'Token expired'
rescue JWT::DecodeError
raise 'Invalid token'
end
end
Explanation: Validates the token signature and expiration, and confirms the account exists.
9. Base API controller with JWT authentication
module Api
class BaseController < ApplicationController
before_action :authenticate_request!
private
def authenticate_request!
token = request.headers['Authorization']&.split(' ')&.last
raise 'Token not provided' unless token
payload = TokenVerifierService.new(token: token).call
@current_account = Account.find(payload['account_id'])
rescue => e
render json: { error: e.message }, status: :unauthorized
end
end
end
Explanation: Base controller that validates JWT tokens before allowing access to protected actions.
10. Tokens controller for token issuance
module Api
class TokensController < ApplicationController
def create
account_id = params[:account_id]
token = TokenIssuerService.new(account_id: account_id).call
render json: { token: token }
end
end
end
Explanation: Generates and returns a JWT token for a given account_id.
11. Example protected products controller
module Api
module V1
class ProductsController < Api::BaseController
def show
product = Product.find(params[:id])
render json: product
end
end
end
end
Explanation: Example resource controller that requires a valid JWT token to access product details.
Final considerations
The JWT token carries important info like `account_id`, so your application can authenticate requests without querying the database every time.
The expiration (exp) claim protects against misuse of the token over long periods.
Using the header `Authorization: Bearer <token>` is the standard way to send the token.
With this base, you can create a Rails API that authenticates systems using JWT tokens generated internally — ideal for system integrations.
Software Engineer | Front-end | React | NextJS | Typescript | NodeJS
2moGreat insights, really appreciated.
Software Engineer | C# | .Net | Backend | Azure | Devops
2moAwesome
Senior Fullstack Software Engineer | TypeScript | NodeJS | ReactJS | NextJS | MERN | Python | AWS
2moWell put, Fabio
Software Engineer @Introhive | Ruby on Rails | Hotwire | AWS | Docker | Postgres
2moVery good, simple and easy to understand
Fullstack Software Engineer | Java | Javascript | Go | GoLang | Angular | Reactjs | AWS
2moThanks for sharing