JWT Authentication with Rails + Ember Part I: Rails Knock

This is a two-part post on building a JWT authentication system in a Rails API + Ember front-end application. Part I will discuss implementing JWT auth from Rails, and Part II will illustrate one approach to implementing JWT auth in Ember, by customizing the Ember Simple Auth add-on

You can see a live demo here and check out the repo for Part I here and Part II here


As Rails 5 comes closer and closer to being released (we're in beta 4 at time of writing!), I've been thinking more and more of (and building more and more with) some of it's big new features. In particular, Rails 5's native API-building capabilities have proven to be really powerful and nice to work with.

Over the past few months, I've built a number of Rails 5 API + Ember front-end applications. For all of these apps, the API and the Ember app have been two separate applications, where the Rails API serves data to the Ember app, using Rack CORS to handle the cross-origin requests.

Authentication when working with CORS always presents an interesting challenge. In a normal (i.e. non-API) Rails app, we "log in" a user by storing their unique user ID in the session store. This means that authentication information is stored on the server side, in the session hash. In other words, our server becomes stateful, keeping track of whether or not a user is "logged in", and who that user is.

What happens in a Rails API, then, when the client is divorced from the server? Well, we'll need to tell the client, i.e. our Ember app, to store some kind of unique identifier and send that unique identifier to the Rails API with every request. Rails can then use the unique identifier, or token, to identify the user making the request.

This is the basic model of Ember Simple Auth. With the Ember Simple Auth add-on, we can authorize our user like this:

  • User "logs in" in via the browser, and Ember grabs the user's email and password and sends them to Rails, requesting authentication.
  • Rails looks up the user. If the user can be authenticated, Rails sends that user's unique token back to Ember.
  • Ember stores that token in the session store, and sends it back to Rails with every subsequent requests.
  • Rails receives any such requests, uses the token to look up the current user and return requested data back to Ember, the client.
  • When someone "logs out", Ember removes the token from the session store, and subsequent requests to rails do not include that token. Rails therefore is not able to find a "current user".

This token-based authentication system is described in an earlier post, if you want to learn more.

One downside of this approach, however, is that it necessitates generating a unique token for every user created, persisting that token in the database, and querying the database using that token every time we want to authenticate a user.

Wouldn't it be great if we could authenticate the user in a stateless manner, without storing tokens in the database? Wouldn't it be even greater if we could do that via requests that are compact and self-contained?

Well, lucky for us, there's JWT Authentication.

What is JWT Authentication?

JSON Web Token (JWT) Authentication is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.*

In plain English––JWT allows us to authenticate requests between the client and the server by encrypting authentication information into a compact JSON object. Instead of, say, passing a user's own unique token (which we would need to persist to the database), or (god forbid), sending a user's email and password with every authentication request, JWT allows us to encrypt a user's identifying information, store it in a token, inside a JSON object, and include that object in every request that requires authentication.

Let's take a closer look at how that cycle might work, using the example of our Ember app + Rails API.

  • User fills out the log in form via the Ember app and hits "log in!"
  • Ember POSTs user's email and password to the Rails API.
  • Rails receives the POST request and and queries the database for the right user. If the user can be authenticated...
  • We'll use JWT to encrypt that user's email and password into a compact and secure JSON Web Token.
  • This token is then included in the response that Rails sends back to Ember.
  • Ember stores the encrypted JWT token in local storage, retrieving it and sending it back to Rails, as the Authentication header in any authenticated requests.

So, what's so great about this system?

Well, for one thing, we are not storing a unique user token in our database. Instead, we are encrypting the user's unique identifying info in our token, and decrypting it on the Rails side when we need to identify the "current user".

If you're not yet convinced of how great this is, check out the jwt.io documentation. It offers some very clear and concise info.

JWT Encryption: How Does it Work?

JWT tokens are encrypted in three parts:

  1. The header: the meta-data describing the encryption algorithm and type of token
  2. The payload: the actual data concerning the user (email, password, etc.)
  3. The signature: special combo of header info + payload to ensure that the sender of the token is really you!

Let's take a look at an example, using the JWT Ruby library to encode our very own token!

Given this information:

  • email: sophie@email.com
  • password: secretpassword
  • hmac secret: $39asdulawk3j489us39vm9370dmsZ
  • encryption algorithm: HS256

We can encrypt our token in the following way:

require 'jwt`

JWT.encode(
  {email: "sophie@email.com", password:   
    "secretpassword"}, 
   hmac,
   "H256")

And it will return our three part JWT:

QyJ0asdfjos.ald925lIn0.eyJ0ZXN0Ijas39uZGF0YSJ9.

Similarly, to decode our token, we can use the following JWT Ruby code, where token is set equal to the above JWT, and hmac is set equal to the hmac secret we used to encrypt that token:

JWT.decode(token, hmac, "H256")

=> [
     {"email"=>"sophie@email.com",      
       "password"=>"secretpassword"},
     {"typ"=>"JWT", "alg"=>"HS256"}
]

As we've seen here, it's not too complicated to implement JWT in Ruby. However, we'll be using the Knock gem in our Rails app to handle encoding and decoding tokens, and identifying/retrieving the current user using JWTs. I just wanted to expose this functionality so that we understand what going on under Knock's hood.

Rails Knock: Implementing JWT in Rails

"Knock Knock" appears to be a terrible horror movie staring Keanu Reeves. That's not what this post is about, sorry Keanu (doesn't he look sad?). Courtesy of youtube.com

Okay, let's get started. This part of the tutorial assumes that you already have a Rails API set up with a User model and users table. Your users table should have a column for email and password_digest. You should have the bcrypt gem in your Gemfile, for encrypting user password, and, (this is important) you should have the has_secure_password password macro––Knock won't work with out it!

First things first, let's add Knock to our Gemfile and bundle install.

# Gemfile

gem 'knock'

Next, we need to add the following macros to our Application Controller:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::API
  include Knock::Authenticable
  before_action :authenticate 

end

Lastly, we need to add the following to our routes.rb file:

# config/routes.rb

Rails.application.routes.draw do
  resources :users
  mount Knock::Engine => "/knock"
end

By mounting the Knock Engine, we're exposing the following endpoint for authentication:

http://localhost:3000/knock/auth_token

Later on, this is the endpoint to which our Ember app will send requests for authorization. Let's take a closer look at what will happen when Ember fires that request.

How Does Knock Work?

First, our Ember app || front end of your choice will fire the following request:

# this is just an example POST request!

POST http://localhost3000/knock/auth_token

Host: 127.0.0.1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
email=sophie@email.com&password=secretpassword
...

That request will be received by the Knock AuthToken Controller, which our app will have access to thanks to our inclusion of the Knock gem.

Let's take a closer look at the Knock source code.

Knock Routes

# knock/config/routes

Knock::Engine.routes.draw do
  post 'auth_token' => 'auth_token#create'
end

We can see that our POST request to /knock/auth_token will be received by the create action of Knock's AuthToken controller. Let's take a closer look at that now.

Knock's AuthToken Controller

# knock/app/controllers/auth_token_controller.rb

require_dependency "knock/application_controller"

module Knock
  class AuthTokenController < ApplicationController
    before_action :authenticate!

    def create
      render json: { jwt: auth_token.token }, status: :created
    end

  private
    def authenticate!
      raise Knock.not_found_exception_class unless user.authenticate(auth_params[:password])
    end

    def auth_token
      AuthToken.new payload: { sub: user.id }
    end

    def user
      Knock.current_user_from_handle.call auth_params[Knock.handle_attr]
    end

    def auth_params
      params.require(:auth).permit Knock.handle_attr, :password
    end
  end
end

Here, the authenticate! method authenticates the user, found using the data from params.

Then, the create method returns a JSON object. This object is simple, it has a key of jwt and a value of the JWT generated by Knock's AuthToken model, with our authenticated user's information.

So, the payload sent back to Ember (or whichever client side framework you may be working with), looks like this:

{jwt: 'fdi38adsvljas839.a893azQ38700.839238asdnsidfaeZTY'}

Before we move on, let's take a look at one more thing that Knock provides for us, the current_user method:

Knock's current_user Implementation

Remember when we included this line in our Application Controller?

include Knock::Authenticable

Well, this is the module in which the current_user magic happens. Let's take a look:

module Knock::Authenticable
  def current_user
    @current_user ||= begin
      token = params[:token] || request.headers['Authorization'].split.last
      Knock::AuthToken.new(token: token).current_user
    rescue
      nil
    end
  end

  def authenticate
    head :unauthorized unless current_user
  end
end

Here, Knock grabs the JWT from the incoming request's Authorization header, and decodes that token, returning the current user.

We will have access to this current_user method in our own controllers, but we will have to make sure we include the Authorization header, containing the JWT, when we send authenticated requests from Ember. We'll talk more about that later.

Okay, now that we understand Knock and agree that it is wonderful, we're ready to move on to Part II: Implementing JWT Auth with Ember Simple Auth

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus