How to secure your RESTful API
Token-based authentication for a RESTful API in Rails; User and Token models, SecureRandom generation, expiration and per-user permission checks.
Tired of getting hacked? Of opening your website the next morning to find a huge 'F*ck you!' in the header? THEN THIS POST IS FOR YOU!!!… Ok, maybe not for you — but if you're interested in authenticating users with tokens in your RESTful API to make it more secure, then you should definitely keep reading.
The hack that taught me about security…
It was a quiet morning at a hospital. I'd been in the programming department for less than a month when suddenly the big 'Boss' called the head of the department because 'someone' had hacked the hospital's website…
…it turned out someone had decided to hack the page and 'break' several tags. To fix it, they simply restored a backup they had saved, and the page was somehow 'rescued'.
Two months later I finished the task I'd been assigned: updating to the newest version of Joomla [yes, they used JOOMLA! as the CMS for their website].

Saturday, 8:00 am. I was sleeping peacefully when the head of the programming department called me and said, in a desperate voice: 'THE SITE'S BEEN HACKED!!!'… From that moment on I understood how important security is ಥ_ಥ
API's and Ruby on Rails…
In one of my previous posts I talked about what an API is and how to build a very simple one in Ruby on Rails. We had a user model with an email and a name:
# my_api/app/models/user.rb
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
validates :name, presence: true
endWe'll add two new models:
$ rails g model Token token expires_at:datetime user:references
$ rails g model Article title body:text user:referencesOur Token model has a 'token' field that will store a random string [generated automatically] and an 'expires_at' field to give our token an expiration time [we don't want anyone to have infinite access to our API].
Our users will use the token to authenticate against our API [in a conventional web application we'd have sessions for our users; in an API that's not possible, so we'll use tokens instead of sessions].
We'll use the token to identify the user who wants to create, update or delete certain articles [only whoever created the article can update or destroy it — after all, we don't want any random user deleting any article].
I won't go into detailed, step-by-step instructions for every single thing, since I'm assuming you already have basic knowledge of or experience with Ruby on Rails [after all, nobody starts learning Rails by building API's, right?].
How will our tokens work?
On every request made to our API [for example, to create an article] the client must send the token. We'll check whether the token has expired or not [our tokens will last at most 3 months; if one expires, the user will have to request a new one], and finally we'll find which user the token belongs to — and it's on behalf of that user that we'll perform the corresponding actions.
It may sound confusing, but it'll make more sense once we look at some code.
Our routes
Nothing out of this world — just the basic routes to make our API work:
Rails.application.routes.draw do
namespace :api, defaults: { format: "json" } do
namespace :v1 do
resources :users, only: [:create] # we only need the 'create' action to add new users
resources :articles, except: [:new, :edit] # we won't need these actions since this is an API
end
end
endUser model
To register users we'll add a class method 'with_token', where the 'data' parameter holds the data of the user to create [something like: params { data: { email: 'giancarlos@devacademy.la', name: 'Giancarlos Isasi' } }].
Just to explain a little: line 9 works like this — first it looks for a user with the email we're sent; if it doesn't find one, it creates a new record with the data specified in the block [first or create: if you don't find it, create one… if the record exists, it returns that object].
# my_api/app/models/user.rb
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
validates :name, presence: true
def self.with_token(data)
User.where(email: data[:email]).first_or_create do |user|
user.email = data[:email]
user.name = data[:name]
end
end
endToken model
We'll create two instance methods: 'valid?' checks that our token hasn't expired, and 'generate_token' runs before a token is created. 'SecureRandom.hex' creates a random hexadecimal string, something like '95c871e0a3285d0b744fea1987c4c439'. And on line 16 we set our 'expires_at' field to 3 months from the current date.
class Token < ApplicationRecord
belongs_to :user
before_create :generate_token
def valid?
self.expires_at > DateTime.now
end
private
def generate_token
loop do
self.token = SecureRandom.hex
break unless Token.where(token: self.token).exists?
end
self.expires_at = 3.month.from_now
end
endUsers Controller
In our create method, we first validate that we're being sent the information of the user to create (params[:user]).
From here on I'll put the explanations as comments inside the code ✍(◔◡◔)
# POST api/v1/users, params: { user: { email: 'giancarlos@devacademy.la', name: 'Giancarlos Isasi' } }
def create
if params[:user]
@user = User.with_token(params[:user]) # we pass the info our 'with_token' method will use to create the user
@token = @user.tokens.create() # we create a new token for the user returned by 'with_token'
render "api/v1/users/show" # this view will display the user's info along with their token
else
render json: { error: 'user params info is missing' } # only if we aren't sent the required info
end
endOur 'show' view looks like this:
# views/api/v1/users/show.json.jbuilder
json.(@token, :token)
json.(@user, :id, :name, :email)If we make a 'POST' request with the corresponding parameters, we get something like this:
{
"token": "8b7794a62dd440db597b3566f95dc6fb",
"id": 1,
"name": "Giancarlos Isasi",
"email": "giancarlos@devacademy.la"
}Our Application Controller
We'll create a method to identify which user the incoming token belongs to. For example, testing with RSpec, our POST request would be:
post 'api/v1/articles', params: { article: { title: 'my first API', body: 'my first api with Ruby on Rails!!' }, token: '8b7794a62dd440db597b3566f95dc6fb' }Notice that we don't send any user data — only their token.
class ApplicationController < ActionController::Base
# protect_from_forgery with: :null_session
protected
def authenticate # we'll use this method in our Articles controller to know which user wants to create an article.
# It also lets us check whether the user owns the article they want to update or delete
token_of_params = params[:token] # we grab the token sent in the request
token = Token.find_by_token(token_of_params) # we check that the token exists in the database
if !token.nil?
if token.valid? # we check the token is valid using the instance method we defined in our Token model
@current_user = token.user # we get the current user from the token that was sent
else
render json: { error: 'token invalid :(' } # error message if the token has expired
end
else
render json: { error: 'token is missing :|' } # error message if the token wasn't sent or doesn't exist in the database
end
end
endFinally, our ArticlesController
It's not as hard as it looks (̶◉͛‿◉̶)
class Api::V1::ArticlesController < ApplicationController
before_action :authenticate, only: [:create, :update, :destroy] # using the method from our ApplicationController we verify the token is valid and authenticate the user (assigned to our @current_user variable)
before_action :set_article, only: [:show, :update, :destroy] # the typical method to find the article, no explanation needed
def index
@articles = Article.all
end
def show
end
def create
@article = @current_user.article.new(quiz_params) # we've already identified the user, so we create the article for them
if @article.save
render template: 'api/v1/articles/show' # we render our show view to display the created article's data
else
render json: { error: @article.errors }, status: :unprocessable_entity # if validations fail, show the errors — though it should pass, since we have no validations :|
end
end
def update
if @current_user == @article.user # we use current_user to verify the user is indeed the one who created the article
@article.update(quiz_params) # once we've validated the user owns the article, we perform the update
render 'api/v1/quizzes/show' # we display the updated data
else
render json: { error: 'you dont have permission' }, status: :unauthorized # if the user isn't the one who created the article, show the errors
end
end
private
def set_article
@article = Article.find(params[:id]) # we identify the article to update or delete
end
def quiz_params
params.require(:article).permit(:title, :body) # the typical strong params, not much to explain
end
endFinally…
So our flow ends up like this: we create a user, receive the token, and then use the token to make our POST, UPDATE or DELETE requests.
WE DID IT! (͡° ͜ʖ ͡°)
I know there are things to improve — doing TDD in the first place, some DRY, better code conventions — but I'll leave it here, otherwise this post would never end [we're already past 1000 words!! How did you read all of this? I admire you, mate — add me on Steam (͡• ͜ʖ ͡•)].
You can also read the following posts:
- What an API is and how to build one in Ruby on Rails
- Don't know how to test a Rails controller? This post is for you!
- 5 tips to start on the right foot with Ruby on Rails
Thanks for reading; if you liked it, share it with your friends! Good luck!
THE END…
