Skip to main content
All Posts
Development

Integrating Rails and Devise with JSON Web Tokens: An Introduction

Authentication is an integral part of many online applications. Typically, you’ll have some form of username/password style, sometimes replacing username with an email address. Regardless of which method is used, you need a way to be able to persist logins across many different actions – in the case of a website, often browser cookies are used; when a web browser sends a request to a server, any cookies defined for the server’s domain are sent back to the server and used to retrieve the user’s session data. This session data will then tell the server which user is actually making the request.

With JSON Web Tokens (JWT), rather than using a cookie, a token is added to the request headers themselves (rather than stored/retrieved as a cookie). This isn’t performed automatically by the browser (as with cookies), but typically will be handled by a front-end framework as part of an AJAX call. This aspect will not be covered in this tutorial but will be covered in a subsequent part 2 at a future date.

We assume that Ruby 2.4.1 and Rails 5.1.3 are already installed (there are many tutorials available already on the web). You can access a more complete, finished version of the related project here if you would like to play around with the code, although it isn’t necessary to follow along with this tutorial. Additionally, the project is deployed here if you would like to test without setting it up locally.

JSON Web Tokens, Rails and Devise

First off, I highly recommend you familiarize yourself with what Rails, Devise, and JWTs are if you haven’t.

In this post, I will be outlining an example project setup as an introduction to integrating the Rails standard authentication system Devise with JWT.

Rails to the Rescue

Ruby on Rails has been around a long time and has matured to the point where it is a rock solid platform for building web applications and is still a very viable platform for this reason. The first thing you’re going to want to do is to make sure you have the latest versions of Ruby and Rails installed. There are already many tutorials out there for this, and the process is different for every platform. Once it’s installed and verified, you’ll want to create a new Rails project. You can call it whatever you want; I just use devise-jwt-example for simplicity.

Once that’s complete, you’ll need to edit the Gemfile to add Devise and JWT. In this example I’m adding them to the end of the file:

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
 
# Additional gems for Devise and JWT
gem 'devise'
gem 'devise-jwt'

Next, you’ll want to run the following commands:

$ bundle install
$ rails generate devise:install
$ rails generate devise User
$ rake db:migrate

Devise configuration files are now created, as well as the User model. You can ignore the various setup messages printed for now.

Open up the config/initializers/devise.rb file. There’s a whole of information in there, but we’re just going to focus on the necessities to getting up and running.

Near the top, you should see a section commented out (the secret key will be different as it changes during installation):

# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
  # The secret key used by Devise. Devise uses this key to generate
  # random tokens. Changing this key will render invalid all existing
  # confirmation, reset password and unlock tokens in the database.
  # Devise will use the `secret_key_base` as its `secret_key`
  # by default. You can change it below and use your own secret key.
  # config.secret_key = 'f88dc3b002b0427e4b762f4936a9c37caa8f6ee9b075cb3e911e3c6c360c431a8502378651305beb190351a3fd63d835855f72b06592e13f2b410862f3bb54f4'

You’ll want to uncomment the `config.secret_key` line, and insert the following like so:

# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
  # The secret key used by Devise. Devise uses this key to generate
  # random tokens. Changing this key will render invalid all existing
  # confirmation, reset password and unlock tokens in the database.
  # Devise will use the `secret_key_base` as its `secret_key`
  # by default. You can change it below and use your own secret key.
  config.secret_key = 'f88dc3b002b0427e4b762f4936a9c37caa8f6ee9b075cb3e911e3c6c360c431a8502378651305beb190351a3fd63d835855f72b06592e13f2b410862f3bb54f4'

  # This configuration is for Devise JWT.
  config.jwt do |jwt|
    # TODO: Assign this to environment variable before production deployment!
    jwt.secret = 'INSERT SECRET HERE'
    jwt.dispatch_requests = [
        ['POST', %r{^/users/sign_in$}],
        ['GET', %r{^/$}]
    ]
    jwt.request_formats = { user: [:json] }
    jwt.expiration_time = 8.hours.to_i
  end

Go ahead and save this file, and then jump back to the command line and execute:

$ rails secret

This will return a long string of numbers and letters – copy and paste that into the section above, replacing INSERT SECRET HERE (leave the quotes).

Open up the config/routes.rb file, and change it as follows:

Rails.application.routes.draw do
  devise_for :users
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: "home#index"
end

Next, modify app/controllers/application_controller.rb so it looks like this:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end

Create a file in app/controllers called home_controller.rb and add the following:

class HomeController < ApplicationController
    def index
 
    end
end

Create a new directory in app/views called home and inside it add a file called index.html.erb:

<h1>Welcome! Now, you can <%= link_to 'Log Out', destroy_user_session_url, method: :delete %></h1>

Finally, open up the User model (app/models/user.rb) and modify the devise declaration so it looks like the following:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null
end

Save that file, then go ahead and start rails:
$ rails server
Open up a browser, and go to: http://localhost:3000/users/sign_in
You should be greeted with the following:

Great! Let’s create an account, and verify that we’re getting our JWT after signing in. Go ahead and click Sign up, and fill in your email and password:

Click Sign up, and you should be taken to another page:

Okay! You have created an account and are signed in, let’s check to see if the JWT has been issued and sent to the browser. I’m working in Chrome, so the process will be different in other browsers.

Open up the developer tools. In the menu area of the tool window, click Network. It will be empty (since the dev tools weren’t open when we loaded the page), so go ahead and refresh, and that window should populate.

Click on the top entry (it will be called localhost if you’re following along), and look at the Response Headers on the right. You should see something like this:

See that Authorization header there?

That is your JWT token for this session!

It doesn’t look like much, and right now the browser doesn’t know how to do anything with it yet, but we’ll be covering that in a future blog post. In the meantime, here are a number of resources that could provide useful in implementing your own solution. Until next time, happy Authenticating!

Author: Richard Foster

Rich Foster, our resident Technologist and CTO, is always looking for new and exciting ways to solve problems through web strategy. Fueled by caffeine and inspired by music, the solutions he engineers for our Clients are pure elegance (with a touch of badassness).

View More Posts from Richard Foster