Community for developers to learn, share their programming knowledge. Register!
Implementing Security in Ruby on Rails

Using JWT for Token-Based Authentication in Ruby on Rails


In today's digital landscape, security is paramount, especially when it comes to user authentication. This article serves as a comprehensive training resource on implementing JSON Web Tokens (JWT) for token-based authentication in Ruby on Rails. Whether you’re looking to secure an API or improve your application’s login mechanism, understanding JWT and its implementation can significantly enhance your security posture.

Understanding JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Components of JWT

A JWT is composed of three parts:

Header: Typically consists of two parts: the type of the token (JWT) and the signing algorithm being used (e.g., HMAC SHA256 or RSA).

Example of a JWT header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload: Contains the claims, which are the statements about an entity (typically, the user) and additional data. There are three types of claims:

Example of a payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

Signature: To create the signature part, you take the encoded header, the encoded payload, a secret, and the algorithm specified in the header. This ensures that the sender of the JWT is who it says it is and that the message wasn’t changed along the way.

Example of creating a signature:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

The final JWT is formed by concatenating the encoded header, payload, and signature using dots (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Advantages of Using JWT

  • Stateless: Since JWTs are self-contained, the server does not need to store any session information, which makes it scalable.
  • Cross-Domain: JWTs can be used across different domains, making them suitable for microservices architectures.
  • Compact: JWTs are URL-safe and can be sent through HTTP headers, making them efficient for web applications.

For more information, visit the official JWT documentation.

Implementing JWT Authentication in Rails

To implement JWT authentication in a Ruby on Rails application, you will typically need to follow these steps:

Step 1: Set Up Your Rails Application

First, you need to create a new Rails application (if you don’t have one already):

rails new jwt_auth_example --api
cd jwt_auth_example

Step 2: Add Required Gems

Next, you need to add the necessary gems to your Gemfile. The jwt gem is essential for encoding and decoding JWTs, while bcrypt is useful for securely storing passwords.

# Gemfile
gem 'jwt'
gem 'bcrypt', '~> 3.1.7'

Then, run bundle install to install the gems.

Step 3: User Model and Authentication Logic

Create a user model where you will handle user registration and authentication:

rails generate model User username:string password_digest:string
rails db:migrate

In the user.rb model, add the following code for password encryption:

class User < ApplicationRecord
  has_secure_password
end

Step 4: JWT Encoding and Decoding

Next, create a service for handling JWT encoding and decoding. This service will be responsible for generating tokens and verifying them.

# app/services/json_web_token.rb
class JsonWebToken
  SECRET_KEY = Rails.application.credentials.secret_key_base

  def self.encode(payload, exp = 24.hours.from_now)
    payload[:exp] = exp.to_i
    JWT.encode(payload, SECRET_KEY)
  end

  def self.decode(token)
    body = JWT.decode(token, SECRET_KEY)[0]
    HashWithIndifferentAccess.new body
  rescue JWT::DecodeError
    nil
  end
end

Step 5: Authentication Controller

Create an authentication controller that will handle user login and token issuance:

rails generate controller Authentication

In the authentication_controller.rb, implement the login action:

class AuthenticationController < ApplicationController
  def login
    user = User.find_by(username: params[:username])
    if user&.authenticate(params[:password])
      token = JsonWebToken.encode(user_id: user.id)
      render json: { token: token }, status: :ok
    else
      render json: { error: 'Invalid credentials' }, status: :unauthorized
    end
  end
end

Step 6: Securing Routes

To secure your routes, you can create a method that checks for the presence of a valid JWT token:

class ApplicationController < ActionController::API
  def authenticate_request!
    @current_user = user_from_token
    render json: { error: 'Not Authorized' }, status: :unauthorized unless @current_user
  end

  private

  def user_from_token
    token = request.headers['Authorization']&.split(' ')&.last
    decoded = JsonWebToken.decode(token)
    User.find_by(id: decoded[:user_id]) if decoded
  end
end

You can then use this method in your protected routes:

# config/routes.rb
Rails.application.routes.draw do
  post 'login', to: 'authentication#login'
  
  # Protected route
  get 'profile', to: 'users#profile', constraints: ->(req) { req.headers['Authorization'].present? }
end

Step 7: Testing Your Implementation

After setting up your application, it's essential to test your JWT authentication. Use tools like Postman or Curl to send requests to your API.

  • Register a new user (implement this functionality in your user controller).
  • Log in using the credentials and receive a JWT.
  • Use the JWT to access protected routes.

Best Practices for Token Management

To ensure the security and integrity of your JWT-based authentication, consider the following best practices:

  • Short Expiration Times: Set a short expiration time for tokens to minimize the risk of stolen tokens.
  • Token Revocation: Implement a token revocation mechanism, such as maintaining a blacklist of revoked tokens.
  • HTTPS: Always use HTTPS to prevent token interception during transmission.
  • Secure Storage: Store tokens securely on the client side. Avoid local storage and use secure, HTTP-only cookies when possible.
  • Algorithm Choice: Use strong signing algorithms such as RS256 instead of HS256 to enhance security.

For further reading on best practices, you can refer to the OWASP JWT Cheat Sheet.

Summary

In this article, we explored the fundamentals of using JSON Web Tokens (JWT) for token-based authentication in Ruby on Rails applications. We discussed the structure of JWTs, implemented JWT authentication, and highlighted best practices for secure token management. By following these guidelines, developers can enhance their applications' security and provide a seamless user experience.

Implementing JWT authentication can be a powerful tool in your security arsenal. As you continue to develop your skills and knowledge in Ruby on Rails, remember that security is an ongoing process. Keep learning and adapting to new challenges in the ever-evolving tech landscape.

Last Update: 31 Dec, 2024

Topics:
Ruby on Rails