From Rails/Angular app to Rails API and Angular App, Part One: setting up API and JSON Web Tokens

Daniel
4 min readFeb 1, 2017

--

All of my apps so far have been full stack rails apps with either jQuery or Angular powering the front end from within the asset pipeline, but I want to start decoupling the front and back ends into separate apps for greater flexibility in the future. I also want to move away from cookies and start using JSON web tokens (JWT) for user authentication, as that seems to be an important pattern to know today. My first mission: set up a Rails API to render an object array as json to an Angular front end after authenticating a user through a JWT.

First, generate a rails app in api mode:

rails g new grocer_api --api --database=postgresql

This generates a new rails app with only the components needed for an API. All I’m doing with this rails app is generating JSON, so no views or associated logic will be needed. For a relatively tiny app like what I’m building, the difference is probably meaningless, but the resulting app will be more lightweight.

Next, uncomment the rack-cors gem in gemfile.rb and add configuration language to the application.rb file per the gem’s instructions:

# Gemfilegem 'rack-cors'# in Application.rb add tp your Application class definition:config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post,
:options]
end
end

This will allow Rails to play nice with client side apps. Now set up User and Product models:

rails g model User email:string password_digest:stringrails g model Product name:string# uncomment 'bcrypt' gem in gemfile
# add has_secure_password to User model
# add a few Product instances to seed file

Create controllers and routes:

# add to routes.rb
post '/signup', to: "users#signup"
get '/products', to: "products#index"
# create UsersController and ProductsController# add a basic index action to ProductsControllerdef index
render json: Product.all
end

So with the above code, anyone can navigate to http://localhost:3000/products and see the rendered JSON for the Product.all array. Now I’ll add JWT’s to the mix. The principle behind JWT’s is to give any user who signs up or logs into our web site a unique token that they will then be required to pass back to us as a header any time they want to do anything that requires authentication. Let’s start by adding the JWT and dotenv gems to our rails app:

#Gemfilegem 'JWT'
gem 'dotenv'

The JWT allows us to create methods to encode and decode JWT tokens choosing from a variety of algorithms, and we use the dotenv gem to store a secret as an environment variable so we can safely push our code up to github. Next I create a file in our lib folder called auth.rb.

require 'jwt'class Auth  ALGORITHM = 'HS256'  def self.encode(payload)
JWT.encode(
payload,
auth_secret,
ALGORITHM)
end
def self.decode(token)
JWT.decode(
token,
auth_secret,
true, { algorithm: ALGORITHM }).first
end
def self.auth_secret
ENV["MY_SECRET_KEY"]
end
end

In this file I create the class Auth along with three class methods, encode, decode, and auth_secret. The method encode takes three arguments: the payload is the data that you want to encode into a token. In our case we are going to use the user’s id. It also takes your choice of algorithm which you can learn about on the JWT website — I chose ‘HS256’ as it seems to be pretty standard. Last, it takes a secret key. From those three ingredients it will create a unique JSON web token based on the payload. Using the dotenv gem, I store my secret key in a .env file which I then register in my .gitignore file. Auth.decode works in similar fashion but in reverse. It takes in your token and returns an array that contains a hash with your user id, token type and algorithm type. We want to select the user hash from that array so we chain .first onto our method.

Now lets’ build barebones users and products controllers to work with these methods.

class UsersController < ApplicationController  def signup
user = User.create(user_params)
jwt = Auth.encode({user: user.id})
render json: {jwt: jwt}
end
privatedef user_params
params.require(:user).permit(:email, :password)
end
end
class ProductsController < ApplicationController def index
token = request.env["HTTP_AUTHORIZATION"]
if token && Auth.decode(token)
render json: Product.all
else
render json: { error: { message: "You must have a valid token"}}
end
end
end

The second line of my users#signup action calls the Auth.encode method and returns a token to the user. Conversely, the products#index action looks for the token in our users’ get request header, and only after successfully decoding it and retrieving a user will it render the products array.

Now I’ll create a barebones Angular app with just enough code to see if this works. Here’s my html file:

<html ng-app="app">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body ng-controller="MainController as main">
<button ng-click="main.signup()">
sign up
</button>
<button ng-click="main.getProducts()">
get products
</button>
<p>
your jwt is: {{ main.token }}
</p>
<ul ng-repeat="item in main.products">
<li>
{{ item.name }}
</li>
</ul>
<script src="js/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/MainController.js"></script>
</body>
</html>

And my app.js file:

angular
.module('app', [])
.controller('MainController', function($http){
var ctrl = this
ctrl.user= {name: "John", email: "johndoe@gmail.com", password:
"password"};
ctrl.signup = function () {
$http.post('http://localhost:3000/signup', { user: ctrl.user
})
.then(function successCallback(response) {
console.log(response.data)
ctrl.token = response.data.jwt
window.localStorage['jwt'] = response.data.jwt;
});
};
ctrl.getProducts = function () {
token = window.localStorage.jwt
$http({
method: 'GET',
url: 'http://localhost:3000/products',
headers: {
'Authorization': token
}
})
.then(function successCallback(response) {
if (response.data.error) {
alert(response.data.error.message)
}
else ctrl.products = response.data
});
};
});

The above is a very down and dirty app that simulates signing into Angular (an actual authentication service will have to wait for another day…). The key thing is see is that inside ctrl.signup() I store the JWT passed to me by the rails server into my local storage, and in ctrl.get products() I pass that JWT back to the server inside a header as part of my GET request. And it works!

--

--