A Simple Token Authentication Service for Mobile Devices Using Rails 3 and Devise.

This post will show you how to implement a simple token based authentication schema for a Rails 3 application. The implementation uses Devise(https://github.com/plataformatec/devise) with the token_authenticable module enabled.

Show me the code: https://github.com/matteomelani/Auth-Token-Service-Prototype

The Problem

Let’s say you have a nice Rails 3 web application that uses Devise for authenticating users and you now want to add a native mobile application to the picture. How do you go about authentication?

The Solution

The solution is to use a simple authentication token protocol.

  1. Client: HTTP GET /api/v1/photos.json
  2. Server: status=401, body={}
  3. Client: display sign in screen to user
  4. User: enter username (email address) and password
  5. Client:  HTTPS POST /api/v1/tokens, body: email=user@emailservice.com&password=userpassword
  6. Server: status =200, {“token”:”ASDERfsfgewrvsa@#$%5″}
  7. Client: HTTP GET /api/v1/photos.json?auth_token=ASDERfsfgewrvsa@#$%5
  8. Server: status=200, list of user’s photo in JSON format

Helpful Tools

  1. Firefox plugin RESTClient (https://addons.mozilla.org/en-US/firefox/addon/restclient/). This is a simple tools that allows you to manually test your REST services. If Windows is your platform Fiddler2 (http://www.fiddler2.com/) is your choice.
  2. Cucumber and Capybara for acceptance testing.

Implementation

Assumptions

  • You have a simple Rails app that allows you to see a list of college courses. You can also add courses.
  • You have set up Devise for the user model so that to view the list of courses you need to be logged in.
  • So you are not logged in

Step 1: Enable Devise token-authenticable module
Change the user table with the following migration

class AddDeviseColumnsToUser < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.token_authenticatable
    end
  end
  def self.down
    t.remove :authentication_token
  end
end

In user.rb add :token_authenticatable to the list of devise modules, it should look something like this depending on what devise module you have already enabled

devise :database_authenticatable,
       :token_authenticatable,
       :recoverable,
       :rememberable
      ,:omniauthable,
       :validatable,
       :trackable,
       :timeoutable,
       :lockable,
       :confirmable


Step 2: Create the routes for the Token service.

namespace :api do
  namespace :v1  do
    resources :tokens,:only => [:create, :destroy]
  end
end

All the API controllers are in a separate directory: …/myapp/app/controllers/api/v1. Having separated set of controllers to serve two different application interface (JSON and HTML) helps to keep maintenance and upgrades simple.

Step 3: Create tokens controller.
Create the tokens controller in /myapp/app/controllers/api/v1/tokens_controller.rb

class Api::V1::TokensController  < ApplicationController
    skip_before_filter :verify_authenticity_token
    respond_to :json
    def create
      email = params[:email]
      password = params[:password]
      if request.format != :json
        render :status=>406, :json=>{:message=>"The request must be json"}
        return
       end

    if email.nil? or password.nil?
       render :status=>400,
              :json=>{:message=>"The request must contain the user email and password."}
       return
    end

    @user=User.find_by_email(email.downcase)

    if @user.nil?
      logger.info("User #{email} failed signin, user cannot be found.")
      render :status=>401, :json=>{:message=>"Invalid email or passoword."}
      return
    end

    # http://rdoc.info/github/plataformatec/devise/master/Devise/Models/TokenAuthenticatable
    @user.ensure_authentication_token!

    if not @user.valid_password?(password)
      logger.info("User #{email} failed signin, password \"#{password}\" is invalid")
      render :status=>401, :json=>{:message=>"Invalid email or password."}
    else
      render :status=>200, :json=>{:token=>@user.authentication_token}
    end
  end

  def destroy
    @user=User.find_by_authentication_token(params[:id])
    if @user.nil?
      logger.info(“Token not found.”)
      render :status=>404, :json=>{:message=>”Invalid token.”}
    else
      @user.reset_authentication_token!
      render :status=>200, :json=>{:token=>params[:id]}
    end
  end

end

Step 4: Manual Testing
You can now use RESTClient to make sure everything works fine. Fire it up and send the first POST request to http://localhost:3000/api/v1/tokens.json. Do not forget to add the email and password to the body.

Note: to have the server see the post params make sure the RESTClient request header contains a parameter with name “Content-Type” and value “application/x-www-form-urlencoded”.

If you now click on RESTClient Response Body tab you can see the returned authentication token.

If you now send a HTTP GET to http://localhost:3000/courses.json?auth_token=<put your token here> you should see the list of courses.