Ruby on Rails Authentication: A JSON Web Token Example

August 15, 2017

Ruby on Rails Authentication: A JSON Web Token Example

So as part of the fair job offer project, a tool to help programmers keep track of their total compensation and make apples-to-apples comparisons of their job offers, I decided to experiment with using JSON web tokens for authentication.

Authorization JWT

What is JWT?

Briefly, JWT (JSON Web Token) is a standard that “is a compact, URL-safe means of representing claims to be transferred between two parties.”

JWT and Oauth2 are two popular ways of adding server side authentication for APIs. I’ll spare you the details of JWT since there are other sources for it. Just think of JWT data represented as an encoded JSON object.

Application Overview

This blog post is intended as a brief tour of the setup required for making JWT token authentication work with a Rails-based Grape API with Angular on the front end. Full source code is located at the fair_offer_calculator repository.

Authentication Data flow

Here is how the authentication scheme with JWT works in this application.

  1. A client username and password gets sent to the backend Ruby API.
  2. If the client credentials are valid, a JWT token is generated.
  3. The client receives the token and stores it somewhere for later use.
  4. If the client makes a request for a protected resource from the server, it includes the token in an HTTP header that includes the user’s id.
  5. When receiving a request, the backend server looks at the JWT token and verifies it was generated for the user represented by the ID in the HTTP payload.

Server side code

A lot of this code is based on a couple of blog posts I found from Adam A and Adrian P.

I’m using the clearance gem by Thoughtbot and an AuthorizationToken module built around the jwt gem for Ruby.

Step 1 – Build an AuthorizationToken Library

The first thing to do is to have an authorization token library that generates an encoded JWT token and checks if one sent back from the client is a valid token.

class AuthorizationToken
  # Encode a hash in a json web token
  def self.encode(payload, ttl_in_min = 60 * 24 * 10)
    payload[:exp] = ttl_in_min.minutes.from_now.to_i
    JWT.encode(payload, Rails.application.secrets.secret_key_base)
  end

  # Decode a token and return the payload inside
  # throw an error if expired or invalid. See the docs for the JWT gem.
  # Note that the decode method could potentially raise on of several exceptions, so any calling code should account for this.
  # source: http://adamalbrecht.com/2015/07/20/authentication-using-json-web-tokens-using-rails-and-react/
  def self.valid?(token, leeway = nil)
    begin
      decoded = JWT.decode(token, Rails.application.secrets.secret_key_base, leeway: leeway)
      HashWithIndifferentAccess.new(decoded[0])
    end
  end
end

Step 2 – API Signup and Login Endpoint

In the below example, you can see, I use the AuthorizationToken module I built in Step 1 for both the signup and login endpoints.

require 'authorization_token'

module JobOfferUser
  class V1 < Grape::API
    version 'v1'
    format :json
    helpers Auth

    helpers do
      def represent_user_with_token(user)
        present user, with: Entities::JobOfferUser, jwt_token: AuthorizationToken.encode({user_id: user.id})
      end
    end

    resource :users do
      params do
        optional :first_name
        optional :last_name
        requires :email
        requires :password
      end

      post "/signup" do
        user = User.new(declared(params))
        if user = user.tap(&:save)
          represent_user_with_token(user)
        else
          error!("Invalid email/password combination", 401)
        end
      end

      params do
        requires :email
        requires :password
      end

      post "/login" do
        if user = User.authenticate(params[:email], params[:password])
          represent_user_with_token(user)
        else
          error!("Invalid email/password combination", 401)
        end
      end

    end
  end
end

Front end code

The front end Angular controller code is a bit more complicated than what is shown below. I’m simply showing the main functionality needed to talk to the signup and login endpoints of the API.

Step 3 - Login controller

In both the login and signup Angular controllers, I use the Restangular library to make HTTP requests to send and receive the JWT token.

function signupCtrl(
  $scope,
  $location,
  localStorageService,
  Restangular,
  AuthFactory,
) {
  $scope.signupUser = function() {
    var signup = Restangular.all('api/v1/users/signup');
    signup.post($scope.user).then(
      function(response) {
        AuthFactory.authenticate(response); //response is basically user object
        $location.url('/fair_offer');
      },
      function() {
        console.log('There was an error in getting jwt token.');
      },
    );
  };
}

function loginCtrl(
  $scope,
  $location,
  localStorageService,
  Restangular,
  AuthFactory,
) {
  $scope.loginUser = function() {
    var login = Restangular.all('api/v1/users/login');
    login.post($scope.user).then(
      function(response) {
        AuthFactory.authenticate(response); //response is basically user object
        $location.url('/fair_offer');
      },
      function() {
        console.log('There was an error in getting jwt token.');
      },
    );
  };
}

Summary

With Rails and Angular, the basic setup for getting JWT authentication working isn’t too bad.

  1. Full source code - fair_offer_calculator repository.

Profile picture

Written by Bruce Park who lives and works in the USA building useful things. He is sometimes around on Twitter.