An Introduction to Using JWT Authentication in Rails

Share this article

An Introduction to Using JWT Authentication in Rails

With the advent of Single Page Applications (SPA) and mobile applications, APIs have come to the forefront of web development. As we develop APIs to support our SPA and mobile apps, securing the APIs has been a major pain area. Token-based authentication is one of the most-favored authentication mechanisms, but tokens are prone to various attacks. To mitigate that, one has to implement ways to fix the issues, which often leads to one-off solutions that make tokens non-exchangeable between diverse systems. JSON Web Tokens (JWT) were created to implement standards-based token handling and verification that can be exchanged between diverse systems without any issue.

What is JWT?

JWTs carry information (called “claims”) via JSON, hence the name JSON Web Tokens. JWT is a standard and has been implemented in almost all popular programming languages. Hence, they can be easily used or exchanged in systems implemented in diverse platforms.

JWTs are comprised of plain strings, so they can be easily exchanged in a URL or a HTTP header. They are also self-contained and carry information such as payload and signatures.

Anatomy of a JWT

A JWT (pronounced ‘JOT’) consists of three strings separated by ‘.’:

aaaaa.bbbbbbb.ccccccc

The first part is the header, second part is the payload, and third part is the signature.

The header consists of two parts:

  • The type of token, i.e. ‘JWT’
  • The hashing algorithm used

For example:

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

The header is base64 encoded which results in:

aeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

This is the first part of the token.

The second part of a JWT is the payload. This part carries the interesting information in the token, also called as JWT Claims. Claims are of three types – private, public, and registered.

  • Registered Claims are claims whose names are reserved but are not mandatory to be used. Examples being – iss, sub, aud, etc.
  • Private Claims are names that are agreed upon between two parties and can collide with other public claims. Must be used with caution.
  • Public Claims – Claims that we can create as per our authentication requirements, such as username, user information, etc.

We can create a sample payload like so:

{
  "iss": "sitepoint.com",
  "name": "Devdatta Kane",
  "admin": true
}

This will be encoded as –

ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ

This becomes the second part of token.

The third and, possibly, the most important part is the Signature. It’s a hash of three components: the header, the payload, and the secret. We run the combined string of the header and the payload through an HMACSHA256 function with ‘secret’ as the server-side secret. Like so:

require "openssl"
require "base64"

var encodedString = Base64.encode64(header) + "." + Base64.encode64(payload);
hash  = OpenSSL::HMAC.digest("sha256", "secret", encodedString)

Since only the server knows the secret, no one can tamper with the payload and the server can detect any tampering using the signature.

Our signature looks as follows:

2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37

Now we have got all three parts of our JWT. Combining the three parts, we get:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ.2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37

This is our complete JWT which can be used for further requests.

Using JWT in Rails

JWT has libraries for almost all platforms and Ruby is no exception. We will create a simple Rails application which uses the excellent Devise gem for authentication and the jwt gem for creating and verifying JWT tokens.

Let’s create a sample Rails application with a contact model and CRUD. The app uses Rails 4.2 and SQlite:

rails new jwt_on_rails

After the application is generated, create a Home controller which we will use to check our authentication. Here is our home_controller.rb in the app/controllers. Like so:

class HomeController < ApplicationController
  def index

  end
end

Map the HomeController to /home in config/routes.rb:

Rails.application.routes.draw do
  get 'home' => 'home#index'
end

Check how its working:

rails s

Point your favorite browser to http://localhost:3000/ and check if everything is working properly.

We have our base application ready. Now, add Devise to our application. First, we will add the Devise and jwt gems in our Gemfile. Like so:

gem 'devise'
gem 'jwt'

Install them using:

bundle install

Now let’s create the Devise configuration files:

rails g devise:install

We will create the Devise User model and migrate the database:

rails g devise User
rake db:migrate

Our User model is in place, which we will use for authentication. It’s time to integrate jwt into our application. First, we will create a class named JsonWebToken in lib/json_web_token.rb. This class will encapsulate the JWT token encoding and decoding logic. Like so:

class JsonWebToken
  def self.encode(payload)
    JWT.encode(payload, Rails.application.secrets.secret_key_base)
  end

  def self.decode(token)
    return HashWithIndifferentAccess.new(JWT.decode(token, Rails.application.secrets.secret_key_base)[0])
  rescue
    nil
  end
end

Add an initializer for including the JsonWebToken class in config/initializers/jwt.rb. Like so:

require 'json_web_token'

We will now add some helper methods in the ApplicationController class which we will use in AuthenticationController class.

In app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  attr_reader :current_user

  protected
  def authenticate_request!
    unless user_id_in_token?
      render json: { errors: ['Not Authenticated'] }, status: :unauthorized
      return
    end
    @current_user = User.find(auth_token[:user_id])
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end

  private
  def http_token
      @http_token ||= if request.headers['Authorization'].present?
        request.headers['Authorization'].split(' ').last
      end
  end

  def auth_token
    @auth_token ||= JsonWebToken.decode(http_token)
  end

  def user_id_in_token?
    http_token && auth_token && auth_token[:user_id].to_i
  end
end

What we’ve done here is added a few helper methods like authenticate_request! which will act as a before_filter to check user credentials. We will create an AuthenticationController to handle all authentication requests to the API. Like so:

In app/controllers/authentication_controller.rb:

class AuthenticationController < ApplicationController
  def authenticate_user
    user = User.find_for_database_authentication(email: params[:email])
    if user.valid_password?(params[:password])
      render json: payload(user)
    else
      render json: {errors: ['Invalid Username/Password']}, status: :unauthorized
    end
  end

  private

  def payload(user)
    return nil unless user and user.id
    {
      auth_token: JsonWebToken.encode({user_id: user.id}),
      user: {id: user.id, email: user.email}
    }
  end
end

Here we have added AuthenticationController to implement the authentication endpoint. It uses Devise to authenticate the user and issue a JWT if the credentials are valid.

We will now update our routes.rb to add the authentication endpoint. Like so:

Rails.application.routes.draw do
  post 'auth_user' => 'authentication#authenticate_user'
  get 'home' => 'home#index'
end

Also, modify the HomeController to secure it using a before_filter and add a meaningful response in case of successful authentication:

class HomeController < ApplicationController
  before_filter :authenticate_request!

  def index
    render json: {'logged_in' => true}
  end
end

Now, create a sample user to test the authentication mechanism using Rails Console.

rails c
rails> User.create(email:'a@a.com', password:'changeme', password_confirmation:'changeme')

Start the server and check out how JWT authentication works:

rails s

Open another terminal and use cURL to test the API. First, try to authenticate without any email or password:

curl http://localhost:3000/home

The response should be {"errors":["Not Authenticated"]} since we have not provided any credentials.

Now authenticate against the API and receive a JWT which we will use for subsequent requests:

curl -X POST -d email="a@a.com" -d password="changeme" http://localhost:3000/auth_user

You’ll receive a successful response along with a JSON Web Token and additional user information. Like so:

{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4","user":{"id":1,"email":"a@a.com"}}

Use our fresh auth_token in the request to /home. Like so:

curl --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4" http://localhost:3000/home

We should receive a successful login response, like:

{"logged_in":true}

Conclusion

We can now utilize this API in any Angular/React/Ember application by storing the issued JWT (in a cookie or local storage) and using it in subsequent requests. This wraps up our tutorial in which we learned how to implement JWT in a Rails application along with Devise. While this tutorial covered just the basics, it is foundational to using JWTs for API authentication.

Hope you liked this tutorial. Comments and feedback welcome, as always.

Frequently Asked Questions about Using JWT in Rails

How do I set up JWT authentication in Rails?

Setting up JWT authentication in Rails involves several steps. First, you need to add the JWT gem to your Gemfile and run bundle install. Next, you need to create a user model and controller, and set up routes for user registration and login. Then, you need to create a JWT token when a user registers or logs in, and send this token back to the user. The user will then send this token in the header of their requests to authenticate themselves. You can decode this token in your application to authenticate the user.

What are the benefits of using JWT for authentication in Rails?

JWT offers several benefits for authentication in Rails. It is stateless, meaning the server does not need to keep a record of which tokens have been issued – it can simply check the signature of the incoming token to verify it. This makes your application more scalable. JWT also allows you to embed user information directly into the token, reducing the need for database lookups.

How secure is JWT authentication?

JWT authentication is secure as long as you follow best practices. This includes using a strong secret key to sign your tokens, and not including sensitive user information in the token payload. It’s also important to set an expiration time on your tokens to reduce the impact of any tokens that might be stolen.

How do I handle token expiration with JWT?

When you create a JWT, you can set an expiration time for the token. If a user tries to authenticate with an expired token, your application should reject the request. You can then prompt the user to log in again to receive a new token.

Can I use JWT with Rails API?

Yes, JWT is a great choice for authentication with a Rails API. Because JWT is stateless, it works well with APIs, which are typically designed to be stateless. You can include the JWT in the header of your API requests to authenticate the user.

How do I test JWT authentication in Rails?

You can test JWT authentication in Rails by creating a request spec that simulates a user logging in and receiving a token. You can then use this token in subsequent request specs to authenticate the user.

Can I use JWT with Devise?

Yes, you can use JWT with Devise. You will need to override some of Devise’s default behavior to return a JWT when a user logs in, instead of using session-based authentication.

How do I decode a JWT in Rails?

You can decode a JWT in Rails using the JWT.decode method. This will return the payload of the token, which you can use to authenticate the user.

What should I do if a JWT is stolen?

If a JWT is stolen, the thief can use it to authenticate as the user until the token expires. Therefore, it’s important to set a short expiration time on your tokens. If a token is stolen, you should prompt the user to log in again and issue them a new token.

Can I include roles and permissions in a JWT?

Yes, you can include roles and permissions in the payload of a JWT. This can be a convenient way to authorize users, as you can check the user’s role or permissions directly from the token, without needing to look up this information in your database.

Devdatta KaneDevdatta Kane
View Author

Devdatta Kane is a software developer and designer based in Pune, India. He works with Radinik Technologies building traceability solutions for a variety of industries. He is also the lead developer of refers2, a CRM for small businesses. He works in Ruby on Rails, but likes to dabble with various new technologies as well. An aspiring photographer and passionate traveler, he loves traveling on his motorcycle, capturing experiences through camera.

GlennGJWTRuby on Rails
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week