[Part 1] Rails 8 Authentication but with JWT

Rails 8 includes its own authentication generator. However, for API-only or RESTful APIs, this authentication scheme is not compatible because APIs are expected to be stateless.

As a result, many developers prefer to modify the default setup to make i…


This content originally appeared on DEV Community and was authored by ruwhan

Rails 8 includes its own authentication generator. However, for API-only or RESTful APIs, this authentication scheme is not compatible because APIs are expected to be stateless.

As a result, many developers prefer to modify the default setup to make it stateless and integrate JWT for authentication. One of the key advantages of JWT is its efficiency—it eliminates the need to access the database on each request to authenticate users.

Setting Up the Project

After creating an API-only Rails project with the command:

rails new rails_jwt --api

navigate to the project root directory:

cd rails_jwt
  • Add Procfile, touch Procfile (optional).
web: bin/rails server -p $PORT
  • Create the required gems, bundle add bcrypt ruby-jwt.
  • Create the database, bin/rails db:create

Building The App

Adding The Model

Adding the model, in this case User model, bin/rails g model User.

The migration:

class CreateUsers < ActiveRecord::Migration[8.0]
  def change
    create_table :users do |t|
      t.string :email_address, null: false
      t.string :password_digest, null: false
      t.timestamps
    end
  end
end

The model:

class User < ApplicationRecord
  has_secure_password

  validates :email_address, presence: true, uniqueness: true
  normalizes :email_address, with: -> (e) { e.strip.downcase }
end

Migrate, bin/rails db:migrate. It will create the users table.

Adding the Authentication Module

app/controllers/concern/Authentication.rb

module Authentication
  extend ActiveSupport::Concern

  included do 
    before_action :authenticate
  end

  class_methods do
    def allow_unauthenticated_access(**options)
      skip_before_action :authenticate, options
    end
  end

  def encode(payload)
    now = Time.now.to_i
    JWT.encode(
      { 
        data: { 
          id: payload.id, 
          email_address: payload.email_address 
        }, 
        exp: now + 3.minutes.to_i,
        iat: now,
        iss: "rails_jwt_api",
        aud: "rails_jwt_client",
        sub: "User",
        jti: SecureRandom.uuid,
        nbf: now + 1.second.to_i,
      }, 
      Rails.application.credentials.jwt_secret, 
      "HS256", 
      { 
        typ: "JWT",
        alg: "HS256"
      }) 
  end

  def decode
    token = get_token
    JWT.decode(token, Rails.application.credentials.jwt_secret, 'HS256')
  end

private
  def get_token
    request.headers["Authorization"].split(" ").last
  end

  def current_user 
    decoded = decode
    decoded.first["data"]
  end

  def authenticate
    begin
      if current_user
        current_user
      else
        render json: { error: "Unauthorized" }, status: :unauthorized
      end
    rescue JWT::ExpiredSignature
      render json: { error: "Token has expired" }, status: :unauthorized
    end
  end
end

Here, the authenticate function will handle error thrown when the token is already expired, and respond with 401.

Next, let's add jwt_secret into our app credentials, since it is required to encode and decode the JWT. Without it, the JWT.encode and JWT.decode will throw an error.

Adding the JWT Secret

in the console, run EDITOR=vim bin/rails credentials:edit, and add the jwt_secret, e.g,

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 32b1ae8aa8953eba641519aba932dc5edf03fe1c9d5b12ca379d7eabe69fde9f4da02717159f2d69ea2ad47eb467edadb40c45160822a2bfe5d55a3ace8fd096
jwt_secret: jwt_secret_jwt_secret

Make the Authentication Available

Add them into ApplicationController,

class ApplicationController < ActionController::API 
  include Authentication
end

The API Endpoint

Let's create the AuthController, bin/rails g controller V1/Auth,

module V1 
  class AuthController < ApplicationController
    allow_unauthenticated_access only: [:create]
    def create
      @current_user = User.find_by(email_address: params[:email_address])
      if @current_user && @current_user.authenticate(params[:password])
        encoded_token = encode(@current_user)
        render json: { token: encoded_token }, status: :ok
      else
        render json: { error: "Invalid email or password" }, status: :unauthorized
      end
    end
  end
end

Now, let's add the route in app/config/routes.rb, so that we will have POST /v1/auth endpoint.

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
  namespace :v1 do
    resources :auth, only: [:create]
  end

  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end

We can test them by:

curl -X POST "http://localhost:5000/v1/auth" -H "Content-Type: application/json" -d "{\"email_address\": \"two@example.com\", \"password\": \"password\"}"

we should get the token in the response body:

{"token":"<AUTH_TOKEN>"}

Conclusion

In this first part, we explored how to implement JWT authentication in a Rails application. However, some functions, such as decode and current_user from the Authentication module, have not yet been fully covered. We will address these and other features in the next part.


This content originally appeared on DEV Community and was authored by ruwhan


Print Share Comment Cite Upload Translate Updates
APA

ruwhan | Sciencx (2025-01-10T01:06:14+00:00) [Part 1] Rails 8 Authentication but with JWT. Retrieved from https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/

MLA
" » [Part 1] Rails 8 Authentication but with JWT." ruwhan | Sciencx - Friday January 10, 2025, https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/
HARVARD
ruwhan | Sciencx Friday January 10, 2025 » [Part 1] Rails 8 Authentication but with JWT., viewed ,<https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/>
VANCOUVER
ruwhan | Sciencx - » [Part 1] Rails 8 Authentication but with JWT. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/
CHICAGO
" » [Part 1] Rails 8 Authentication but with JWT." ruwhan | Sciencx - Accessed . https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/
IEEE
" » [Part 1] Rails 8 Authentication but with JWT." ruwhan | Sciencx [Online]. Available: https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/. [Accessed: ]
rf:citation
» [Part 1] Rails 8 Authentication but with JWT | ruwhan | Sciencx | https://www.scien.cx/2025/01/10/part-1-rails-8-authentication-but-with-jwt/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.