Skip to content

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.

6 min read
  • ruby
  • rails
  • api
  • security

Originally published on Medium

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].

Think it over

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
 
end

We'll add two new models:

$ rails g model Token token expires_at:datetime user:references
$ rails g model Article title body:text user:references

Our 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
end

User 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
 
end

Token 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
end

Users 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
end

Our '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
 
end

Finally, 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
end

Finally…

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:

Thanks for reading; if you liked it, share it with your friends! Good luck!

THE END…

Thanks for reading