JWT Authentication in Rails APIs — Getting Started with System Integration

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?

  • Enables decoupling between systems (frontend, backend, external services)
  • Makes the API scalable and distributed
  • Simplifies authentication for automated system integrations


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:

  • HS256 (HMAC SHA-256): uses a shared secret key to sign and verify tokens. It is fast and simple, ideal when the issuer and verifier are the same application.
  • RS256 (RSA SHA-256): uses an asymmetric key pair with a private key to sign and a public key to verify. Useful when multiple systems need to verify tokens without access to the secret key.

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.

Fabricio Dorneles

Software Engineer | Front-end | React | NextJS | Typescript | NodeJS

2mo

Great insights, really appreciated.

Guilherme Meirelles

Software Engineer | C# | .Net | Backend | Azure | Devops

2mo

Awesome

Matheus Wagner

Senior Fullstack Software Engineer | TypeScript | NodeJS | ReactJS | NextJS | MERN | Python | AWS

2mo

Well put, Fabio

Luis Felipe

Software Engineer @Introhive | Ruby on Rails | Hotwire | AWS | Docker | Postgres

2mo

Very good, simple and easy to understand

JUNIOR N.

Fullstack Software Engineer | Java | Javascript | Go | GoLang | Angular | Reactjs | AWS

2mo

Thanks for sharing

To view or add a comment, sign in

Others also viewed

Explore topics