Rails API With JWT Authentication

9 Min. Read
Dec 8, 2019

What Is JSON Web Token Authentication?

JSON Web Token authentication also known as token-based authentication is a new way of handling the authentication of users in applications. It is an alternative to session-based authentication.

The most notable difference between the session-based and token-based authentication is that session-based authentication relies heavily on the server. A record is created for each logged-in user.

Token-based authentication is stateless - it does not store anything on the server but creates a unique encoded token that gets checked every time a request is made.

Unlike session-based authentication, a token approach would not associate a user with login information but with a unique token that is used to carry client-host transactions. Many applications, including Facebook, Google, and GitHub, use the token-based approach.

JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA. Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

What Does JSON Web Token Contain?

The token is separated into three base-64 encoded, dot-separated values. Each value represents a different type of data:

Header

Consists of the type of the token (JWT) and the type of signing algorithm (HMAC, SHA256 or RSA) encoded in base-64.

For Example:

Payload

The second part of the token is the payload, which contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private claims.

- Registered claims:

These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some of them are: iss (issuer), exp (expiration time), sub (subject), aud (audience), and others.

- Public claims:

These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace.

- Private claims:

These are the custom claims created to share information between parties that agree on using them and are neither registered or public claims.

Payload example:

Signature

To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

The signature is used to verify the message wasn’t changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.

The following shows a JWT that has the previous header and payload encoded, and it is signed with a secret.

How JSON Web Token Work?

The way token-based authentication works is simple. The user enters his or her credentials and sends a request to the server. If the credentials are correct, the server creates a unique HMACSHA256 encoded token, also known as JSON web token (JWT). Since tokens are credentials, great care must be taken to prevent security issues. In general, you should not keep tokens longer than required.The client stores the JWT and makes all subsequent requests to the server with the token attached in the Authorization header. The content of header should look like the following:

1
  Authorization: Bearer <token>

This can be, in certain cases, a stateless authorization mechanism. The server’s protected routes will check for a valid JWT in the Authorization header, and if it’s present, the user will be allowed to access protected resources. If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case or JWT is compare with the one it has stored in the database.

If the token is sent in the Authorization header, Cross-Origin Resource Sharing (CORS) won’t be an issue as it doesn’t use cookies.

Setting Up JWT Authentication In Rails

Lets start with creating a Rails API only application:

1
rails new api-jwt --api

By appending --api at the end of the generator, an API-only application will be created. An API application is a trimmed-down version of standard Rails application without any of the unnecessary middleware, such as .erb views, helpers, and assets. API applications come with special middlewares such as ActionController::API, request throttling, easy CORS configuration and other custom-waived features for building APIs.

Creating the User Model

First create the user model with devise, considering you have already installed devise in your application

1
rails g devise User

Then run the migration

1
rails db:migrate

Encoding and Decoding JWT Token

Once the user model is done, the implementation of the JWT token generation can start. First, the jwt gem will make encoding and decoding of HMACSHA256 tokens available in the Rails application. So,at first

1
gem 'jwt'

Then

1
bundle

Once the gem is installed, it can be accessed through the JWT global variable.

Creating Controller

Let’s start with generating the token using jwt gem when a client enter their credentials and send them back the generated token. For that, lets make a session controller to perform login operation.

1
rails g controller api/sessions

This will make a session_controller.rb under api directory where we will write our token generating code using jwt gem.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Api::SessionsController < ActionController::API
  def create
    @user = User.find_by(email: params[:email])
    if @user&.valid_password?(params[:password])
      jwt = JWT.encode(
          { user_id: @user.id, exp: (1.hour.from_now).to_i },
          Rails.application.secrets.secret_key_base,
          'HS256'
      )
      render json: { token: jwt, user: @user.as_json }
    end
  end
end

So what we basically did here is, at first we find the user by email when client enter their credentials. Then we check if the password sent by client is valid or not. After its validation, we generate the token using JWT global variable as given by jwt gem.

1
JWT.encode payload, secret, encryption_algorithm

We can include any information inside payload as we have already mentioned above in payload topic, here we have passed the user id and expiration time for token.

Here we have used signature as a base-64 encoded version of the Rails application’s secret key (Rails.application.secrets.secret_key_base). Because each application has a unique base key, this secret key serves as the token signature.

‘HS256’ is HMAC using SHA-256 hash algorithm for encryption.

And at last we give back the client with token and user information. Now whenever client request for any information it sends this token along with it and gets back the required information if the token is valid which is checked on the server side.

Checking the token

Lets make a controller named api which will authenticates every request made checking the token send by the client.

1
rails g controller api/api

Now our session controller and any other controller that give back the data to client should be inherited from this controller. So our previous session controller will now need a little modification.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Api::ApiController < ActionController::API
  before_action :user_token_authentication

  private

    def current_user
      header_token = request.headers[:HTTP_AUTHORIZATION]
      if header_token
        token = header_token.split(' ').last
        begin
          decoded = JWT.decode token, Rails.application.secrets.secret_key_base, true, { algorithm: 'HS256' }
          user = User.find(decoded.first["user_id"])
          user
        rescue JWT::ExpiredSignature
          render json: {error: 'Token has expired'}
        end
      else
        nil
      end
    end

    def user_token_authentication
      unless current_user
        render json: { error: 'Invalid token' }
      end
    end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Api::SessionsController < Api::ApiController
  skip_before_action :user_token_authentication, only: :create
  def create
    @user = User.find_by(email: params[:email])
    if @user&.valid_password?(params[:password])
      jwt = JWT.encode(
          { user_id: @user.id, exp: (1.hour.from_now).to_i },
          Rails.application.secrets.secret_key_base,
          'HS256'
      )
      render json: { token: jwt, user: @user.as_json }
    end
  end
end

In api_controller.rb, we have perform the before_action :user_token_authentication which will verify the token at first. Then inside of user_token_authentication method we have checked current_user method which will return invalid token response if its false.

current_user method job is to first extract the token and then decode it to get the payload we have sent earlier and on the basis of that payload it identifies the user. There is also a exception handling for the expiration of JWT token. If the token is valid and user is identified, it return user else nil is returned.

Now lets create another model named Hotel and controller hotels_controller.rb to provide the hotel details of the specific identified users.

1
rails g model Hotel name
1
rails g controller hotels

In hotels_controller.rb :

1
2
3
4
5
6
class Api::HotelsController < Api::ApiController
  def index
    @hotels = current_user.hotels.all
    render json: @hotels
  end
end

Lets create a seed file to perform the authentication for our application.

In seeds.rb:

1
2
3
4
5
6
7
8
9
10
11
12
user1 = User.create email: 'sumin@gmail.com', password: 'password'
user2 = User.create email: 'james@gmail.com', password: 'password'

hotels1 = ['hyatt', 'solti']
hotels1.each do |hotel|
  Hotel.create name: hotel, user_id: user1.id
end

hotels2 = ['everest', 'annapurna']
hotels2.each do |hotel|
  Hotel.create name: hotel, user_id: user2.id
end

To access these resources our routes.rb will look like this:

1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  devise_for :users
  namespace :api, :defaults => { :format => 'json' } do
    post :sign_in, to: 'sessions#create'
    resources :hotels
  end
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Snapshots

Now lets look at our snapshots where i have performed the requests to our application using postman

login for sumin@gmail.com

sumin@gmail.com token send in Authorization header as Bearer Token to get hotel information

hotel information for sumin@gmail.com

login for james@gmail.com

james@gmail.com token send in Authorization header as Bearer Token to get hotel information

hotel information for james@gmail.com

when request performed without token

when token has expired