Blog Feature

By: Tim Upchurch on May 11th, 2017

Print/Save as PDF

JWT Fundamentals: How To Easily Share UI Components

development | jwt | rails | wordpress

When developing a web application, it is often appropriate to divide responsibilities across different "services" that each handles discrete functionality. While this can provide flexibility in the behavior of an application, consistency across branded UI components becomes difficult, as each service needs to maintain its own set of component markup, styles, and scripts.

 

Background


As a web development consultancy, we are often presented with the case of a client wanting to have control over the marketing content that frames their application. This request introduces an opportunity to split the application's responsibilities, breaking away from our base stack - Ruby on Rails - and bringing in something more tailored to content management - like WordPress. Copy edits can be deferred to the client, who now has a GUI backend appropriate for working with content.

Splitting the application introduces more out-of-the-box functionality, but as mentioned, we've now created potential problems with maintaining consistency across front-end components. To talk through our method of addressing this problem, let's look at this scenario in isolation.

Imagine that a large application is comprised of two separate parts - our distinct services. The first, named "rhino", is a database driven Ruby on Rails application. The second component, referred to as "hippo", is a landing page, simply using JavaScript to provide functionality - this will stand in for WordPress, as we'll only need to work with client-side functionality.

 

The Problem


In this fictitious scenario, hippo and rhino are designed to do what they do well; rhino, to handle the data model and business logic therein, and hippo, to be slick, fast, and JavaScript-y. Perhaps more symbolic animal names could have been chosen to highlight this distinction, but elephant and cheetah take too long to type, so we're going to roll with it.

In both applications, the UI includes a header that displays the user's email address and avatar when they are currently signed in. Since hippo is not formally connected to our server and database, we need another method so we are able to accomplish two tasks:

 

  1. Determining whether or not a user is in a "logged-in" state. Have they recently been authenticated by our application?

  2. Fetching the user specific information for our UI- the user's email address and avatar.

 

Lucky for you and I both, JWT or JSON Web Token or JavaScript Object Notation Web Token (for those not into the whole brevity thing) provides a perfect specification to handle these tasks. JWT is a way to transfer JSON between two parties, signed and secured with a secret or key pair, so the data is verifiable and valid.

In the context of our applications, upon successful authentication of a user, rhino can store a JWT in local storage and then hippo can read this token and pass it back to our application in the authorization header of an API request - receiving the user's information in the success payload.

 

Application Setup


Going into construction specifics is beyond the scope of this post - but in brief - rhino is a Rails application tied to a PostgreSQL database. There is a user model, and the application leverages the Devise gem for its user based authentication strategy. Rhino is based on Slining, Vaporware's Rails application starter that includes many standard defaults and assumptions.

Both applications utilize the Bootstrap framework for front-end styling. Hippo is far simpler in its construction - a static HTML page tied to a local JavaScript file.

 

The applications are available on GitHub for demonstration at vaporware/rhino and vaporware/hippo.

 

The base applications, prior to implementing JWT authentication, are available in their respective master branches, and the complete applications in their jwt branches. Follow along if you'd like.

 

Creating the JWT


We'll start with rhino, which will run point for the majority of the JWT strategy. To work with JWT, we can use the jwt-ruby gem - a pure ruby implementation of the JWT specification.

Add the following to your Gemfile.

gem 'jwt'

And run bundle install

 

Next, we'll create a JsonWebToken class in lib/json_web_token.rb that will encode and decode provided tokens based on a Rails secret key.

 

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

 

We'll also need an initializer to load the JsonWebToken class in config/initializers/jwt.rb:

 

require 'json_web_token'

 

As Devise is based on Warden, we can implement a callback after a user is set during the authentication cycle. Warden hooks need to be required when the application boots, so we'll add these to the Devise initializer in config/initializers/devise.rb.

 

...

Warden::Manager.after_set_user do |user,auth,opts|
  auth.cookies[:jwt_access_token] = { value: JsonWebToken.encode({user_id: user.id}) }
end

Warden::Manager.before_logout do |user,auth,opts|
  auth.cookies.delete :jwt_access_token
end

 

Additionally, we can use a Warden callback to delete the previously set access_token when a user signs out.

 

Try logging in with a test user account. As you'll see in the developer tools application inspector, our newly created jwt_access_token is present with a properly formatted, three-part JWT value.

 

token set on login

 

Consuming the JWT


Briefly, to share user specific information with our client services, we'll need to authenticate API requests to the app against our JWT strategy.

 

In app/controllers/application_controller.rb we'll create an authenticate_request! helper method that can be called as a before_action on any controller method that we want to authenticate.

 

...

protected
  def authenticate_request!
    unless user_id_in_token?
      auth_error
      return
    end
    @current_user = User.find(auth_token[:user_id])
  rescue JWT::VerificationError, JWT::DecodeError
    auth_error
  end

private
  def auth_error
    render json: { errors: ["Not Authenticated"] }, status: :unauthorized
  end

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

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

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

 

With our method to properly authenticate requests against a JWT in place, we can implement the API for serving the user specific UI component information to our client service, hippo.

 

To handle incoming requests from hippo for UI component information, we'll create app/controllers/ui_components_controller.rb where we'll have a navbar action.

 

class UiComponentsController < ApplicationController

  def navbar
    respond_to do |format|
      format.json do
        render json: navbar_template.to_json
      end
    end
  end

  private

    def navbar_template
      render_to_string({template: 'application/_navbar', layout: false, formats: [:html]})
    end
end

 

And, add the corresponding entry in config/routes.rb.

 

get "/navbar", to: "ui_components#navbar"

 

Hit the navbar endpoint at localhost:5000/navbar.json. You'll see our navbar template represented as a string- currently in the state before a user has logged in, so no user email or avatar information present.

 

Let's add our authentication helper to app/controllers/ui_components_controller.rb and see how the response changes.

 

class UiComponentsController < ApplicationController

  before_action :authenticate_request!

...

 

After adding the authentication helper, and re-sending the request to /navbar.json, our response is returned status 401 Unauthorized, with the message {"errors":["Not Authenticated"]}. Perfect. No JWT present in the authorization header, no user data granted.

 

Sending the JWT


Until this point, we've been working with rhino, our server-side Rails application. The next phase of our project will involve hippo, our static landing page. Hippo is available on GitHub at vaporware/hippo. The initial version prior to implementing JWT is available in the master branch, and the finished version in the jwt branch.

 

Hippo is built using the Jekyll static site generator. To serve the project locally, run jekyll serve in the project's root directory.

 

The first step in calling rhino with our JWT authorization header, is to pull the JWT from browser's local storage. Ensure the JWT is present by logging in to rhino, and checking the application inspector. With the JWT present in the browser, let's script.

 

Create navbar.js in assets/js.

 

We'll need to add the script manually to includes/head.html as hippo has not implemented an asset pipeline.

 

...

<script type="text/javascript" src="/assets/js/vendor/bootstrap.min.js"></script>

<script type="text/javascript" src="/assets/js/navbar.js"></script>

...

 

Back in assets/js/navbar.js add the function getCookie() that will search the document's available cookies to match a given string and return the cookie's value. We'll call this function and store the result in a variable called token.

 

function getCookie(key) {
  var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
  return keyValue ? keyValue[2] : null;
}

var token = getCookie("jwt_access_token");

 

With the JWT pulled from the browser, we can make our call to rhino using jQuery's ajax function.

 

...

var request = jQuery.ajax('localhost:5000/navbar', {
  type: 'GET',
  headers: {
    'Authorization': "Bearer " + token
  },
  success: function(result){
    console.log(result);
  },
  error: function() {
    console.log("error!");
  }
});

 

On the first try, we are met with a CORS error. We'll need to define a CORS policy within rhino before we'll be allowed to call across different domains- in our case localhost:5000 and localhost:4000.

We can define a CORS policy via Rack middleware, using the rails-cors gem.

 

Add gem 'rack-cors', :require => 'rack/cors' to your Gemfile and run bundle install.

 

Now, we can add a CORS policy to config/application.rb.

 

...

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:4000'
    resource '*', :headers => :any, :methods => [:get, :delete, :options]
  end
end

...

 

Restart the rails server and return to hippo, and let's try the ajax call again. Success! 200 a-OK.

 

Placing the Shared Component


The last step in presenting our shared component is actually placing it in the DOM. In includes/header.html there is a header tag, to which we'll assign id="shared-header-wrapper". This is the landing zone for our incoming navbar data.

In assets/js/navbar.js we'll create a function called setHeader() that will leverage jQuery to swap our shared-header-wrapper html with our returned navbar.

 

...

function setHeader(header) {
  if (header != null) {
    $('#shared-header-wrapper').html(header);
  } else {
    $('#shared-header-wrapper').html("");
  }
}

 

Remove the console logging from the ajax request success callback, and add the call to setHeader().

 

...

var request = jQuery.ajax('http://localhost:5000/navbar', {
  type: 'GET',
  headers: {
    'Authorization': "Bearer " + token
  },
  success: function(result){
    setHeader(result);
  },
  error: function() {
    console.log("error!");
  }
});

 

Reloading the hippo root page, we should now see our shared navbar in the header location. The user email address and avatar will be the same as on rhino.

 

Let's review...

When a user logs in to our primary application, rhino, a JWT is created. The JWT encodes a reference to the user, and is saved in the user's browser. If and when the user browses to our secondary application, hippo, the JWT is pulled from the browser's cookies. An API call is made to rhino to request component information with the JWT added to the authorization header. Rhino then decodes the JWT, using the supplied user identifier to retrieve the appropriate information, and return it to hippo in the success payload. Of course, this is a fairly trivial example, but you can imagine that this pattern can be extended to other purposes.

 

If you want to learn more about the JWT specification, the introduction page on Auth0's site JWT.io provides a great overview of the methodology. Questions about the code or running into problems? Leave us a comment below or shoot us an email at hello@vaporwa.re and we'll take a look!

About Tim Upchurch

Tim is a trained designer turned front-end developer and coding instructor. Specializing in front-end details, Tim provides excellent development, testing, and support. Having an insatiable curiosity, Tim is eager to approach new technology and is excited about the continual evolution of the web. Offline, Tim enjoys every aspect of North Carolina's beautiful and diverse landscape - hiking and skiing while in the mountains, surfing and fishing at the coast.