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.

About these ads

45 thoughts on “A Simple Token Authentication Service for Mobile Devices Using Rails 3 and Devise.

  1. In tokens_controller.rb,

    replace
    @user.ensure_authentication_token
    with
    @user.ensure_authentication_token!
    at line 20

    Otherwise the new token will never be saved into the database. I was wondering why the login kept failing but it wasn’t difficult to identify the root cause after checking the database. Nevertheless thank you so much for this blog post. I still need to figure how to override other Devise controllers but the approach should be similar.

    • Thanks for spotting the bug.

      To override other Devise controllers you can follow the steps outlined at the Devise Github page:
      Configuring controllers

      If the customization at the views level is not enough, you can customize each controller by following these steps:

      1) Create your custom controller, for example a Admins::SessionsController:

      class Admins::SessionsController { :sessions => “admins/sessions” }

      3) And since we changed the controller, it won’t use the “devise/sessions” views, so remember to copy “devise/sessions” to “admin/sessions”.

      Remember that Devise uses flash messages to let users know if sign in was successful or failed. Devise expects your application to call “flash[:notice]” and “flash[:alert]” as appropriate.

      Hope this help.
      -Matteo

  2. Hi,

    thanks for a great tutorial! One question; how is the destroy intended to be used?

    It seemed awkward to provide a random id to the request which is needed due to the resources route.

    Instead I changed the routes to:
    namespace :api do
    namespace :v1 do
    match “tokens” => ‘tokens#create’, :via => :post
    match “tokens/:token” => ‘tokens#destroy’, :via => :delete
    end
    end

    then I can send a HTTP DELETE to http://localhost:3000/api/v1/tokens/tAReufArxVh5z1ogpngf.json to destroy the token tAReufArxVh5z1ogpngf

    I also saw that you included “resources :courses,:only => [:index]” in the routes.rb but that would require a courses_controller in myapp/app/controllers/api/v1/, right?

    I also changed the destroy method so it will handle a destroyed token. And there is a typo, the table created is called authentication_token so User.find_by_token does not work:
    def destroy
    @user=User.find_by_authentication_token(params[:token])
    if @user.nil?
    logger.info(“Token not found.”)
    render :status=>404, :json=>{:message=>”Invalid token.”}
    return
    end
    @user.reset_authentication_token!
    render :status=>200, :json => {}
    end

    Thanks again,
    Kalle

    • >>> One question; how is the destroy intended to be used?

      The destroy should be invoked by a DELETE request from the client when it logs out. This will tell
      the server to destroy the token thus reducing the surface attack of the service.

      >>> It seemed awkward to provide a random id to the request which is needed due to the resources route.
      >>> ….
      >>> then I can send a HTTP DELETE to http://localhost:3000/api/v1/tokens/tAReufArxVh5z1ogpngf.json to destroy the token tAReufArxVh5z1ogpngf

      Using the token id as: http://localhost:3000/api/v1/tokens/tAReufArxVh5z1ogpngf.json
      works fine: the point is to add the token to the request one way or the other. I prefer
      to add the token as a parameter because it makes it more consistent with other requests.

      >>> I also saw that you included “resources :courses,:only => [:index]” in the routes.rb but that would require a courses_controller in myapp/app/controllers/api/v1/, right?

      The CoursesController is part of the example code you can download from Github.

      -Matteo

  3. Why is the GET method once you have the auth token over HTTP? Cant people easily obtain the token and access to the content that way?

    • The first request, when the client post the username and password should go over HTTPS the other one through HTTP.
      For simplicity I have not used HTTPS in the first request.

      In general you are right. If an attacker intercepts the token it can use to impersonate the user. That is why is important
      to have short lived tokens. The solution is to have the entire protocol over HTTPS for securing the channel. That is
      certainly possible, it is just a matter if the value of the data is worth the extra cost in reduced server side scalability.
      In other word that will depend on the specific application.

      • Passing auth_token as URL parameter is not a good practice. Many web servers (https end points) could log the requests (URL), which may again be a potential exploit. A better approach is to start using Authorization request header.

      • That is right however I rather sacrifice a bit for security for ease of testability. Also having the auth token in the url can really help caching request per user. This days I am working in building a caching system for an iPhone app that keep polling the server, the data are specific to each user but since the auth. token is in the url the system can cache the request using the url as key.

  4. I think it is legal to send DELETE with params.

    I also think that the arguments expressed in http://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request do not really hold in the case the DELETE params is only used for authentication and authorization. In any case you can move the auth token to the HTTP header if you like. To me it just make things a bit more complicated.

    Amazon does a great job: http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html?r=9918

  5. I’m seeing an odd issue with tokens#create where the POST response is a 302 redirect to the root_url (and returning HTML instead of the proper JSON). It’s like it thinks the user is already logged in.

    I believe this redirect happens if you include a valid token with the tokens#create POST params, but I’m pretty sure this isn’t happening (I don’t see it in the incoming params). Do you know what else could cause this redirect to happen?

    Thank you.

  6. Thanks for the post, it really helped!

    One thing I had to slightly change to get the code to work was to rename the param used in the destroy action to :id – if I left it as :token the user wasn’t signed out.

    Cheers,
    Ross

  7. For mongoid users :

    – don’t forget to add field :authentication_token, :type => String in user.rb
    – change @user=User.find_by_email(email.downcase) by @user = User.find_by(email: email.downcase)

  8. There is a typo on line 22 and 31. I believe it should be “Invalid email or password” rather than “Invalid email or passoword”. Thanks for the great post.

  9. Pingback: IOS App + Rails Backend = Como los conecto? | RIA and Mobile Developer

  10. Question..
    I’ve noticed that the token that I’m passing to the user, is stored in the database in raw format.
    So if I got a token “1234” then sitting in the field is the code “1234”.. Which means anyone who can see my database now has access to every user account. Even if they don’t have the code turned on.
    I even entered a bit of sql to change the value of one of these to 1234 (same as my luggage) and now I can change records and data, without ever touching the password field..

    This essentially turns off all security? Am I wrong?

  11. Sorry if the question comes to the wrong place. I’m looking at the token that I just created, and at the user table in my database. What I notice is that for a given user, the token in the database is the same as the token we presented.
    Does this not strike anyone as a huge hole in security? Anyone could gain access to the database and change these values to something they want.
    I did.. I changed the value in my favorite user to “1234” and proceeded in logging in and changing records with that token. I also took a look at some of the other workers in my office and wrote down their tokens.. Just in case I want to blackmail them later.
    One of them didn’t have a token, so I just filled in the field with “haha” and I went to town.
    The whole thing left me feeling very insecure about this method of validation.

  12. Very useful information here. One thing that might have been forgotten is the addition of:

    config.token_authentication_key = :auth_token

    into /config/initializers/devise.rb

  13. i’m trying this:

    require ‘spec_helper’

    describe Api::V1::TokensController do
    describe “POST create” do
    let(:user){ Fabricate(:user, password: “password_1″, password_confirmation: “password_1″) }

    it “get valid key for user” do
    post :create, {:email => user.email, :password => “password_1″}, {:content_type => “application/json”}
    response.code.should eq(200)
    end
    end

    end

    but get error NoMethodError: undefined method `authenticate’ for nil:NilClass

  14. Nice article, Matt.

    Just a question: what is the advantage of using this token-based approach over HTTP-basic authentication (over HTTPS, of course)?

    It seems to add a bit of complexity overhead, as well as possibly being less secure (since the token is passed around possibly in plain-text).

    Thanks!

    • The nice thing about an auth token is that it can be expired after a short amount of time. The quicker the auth token expires the more secure your system is. Obviously the life time of a token needs to be balanced with the UX. For a banking app is ok to ask the user to re-login after 5 minutes of inactivity for a social app is not. HTTP basic-authentication sends the user credentials in the clear for each request, you cannot really expire the user credentials.

      The most secure solution is to use https and put the auth. token in the http request header. If you are app has very strict security requirements you can destroy tokens (log users out) every 5 minutes. What is what backing apps seems to be doing.

      Little less secure but a bit more testable, as well as less annoying to users, you can have all the request over https, pass the auth. token in the url path and expire tokens every 1-2 weeks. This is a very solid solution for most applications.

  15. Pingback: rails devise token authentication for mobile/javascript authentication « Lost Ferry

  16. Just a note that you don’t discuss the issue of rate-limiting certain IP addresses or other methods to foil a brute force attack (made even easier if someone guesses or notes on forums or elsewhere a correct username to start with) against all possible passwords hitting your token authenticate service.

    Might be worth exploring/explaining how you’d handle that. Thanks for the great post!

  17. Thanks for the help!
    I had to replace the quotation marks in the destroy (“Token not found.” with ” instead of “) in order for it to work.
    Besides that, perfect ;)

  18. Great! Really helpful!
    However, I have an issue and I don’t know why.
    When I call http://sub.domain.com:3000/api/V1/tokens.json in POST method with correct body, I receive back:
    “Started POST “/api/V1/tokens.json” for 78.192.244.8 at 2013-02-12 14:12:09 +0100
    ActionController::RoutingError (No route matches [POST] “/api/V1/tokens.json”):”

    But when I run “rake routes” I can see :
    api_v1_tokens POST /api/v1/tokens(.:format) api/v1/tokens#create
    api_v1_token DELETE /api/v1/tokens/:id(.:format) api/v1/tokens#destroy

    Any idea ?

  19. awesome post. thank you very much! FYI for anyone out there getting Invalid Byte Sequence errors – when I copied the controller code into my code editor, some of the quotes were ASCII encoded which resulted in an invalid byte sequence error.

  20. Pingback: In which cases do you need to implement these complex token authentication on devise (Rails 3.2) | Technology & Programming

  21. Pingback: Ruby on Rails web service for user login for mobile app | My CMS

  22. Hi,

    Great post! This is the thing i had been looking for. You have saved my life. But, i do a question that i am working on a Rails project alongwith an mobile app. It has authentication Devise and access control by CanCan. I want to know how i can role based access control by using CanCan in mobile app. Please help me.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s