Designing Rails API using Rabl and Devise

Most of us are aware that enabling API access for rails application is easy as Rails provides RESTful APIs by default. However, a little complexity arises when some responses are expected in xml format (maybe for some legacy system) and JSON format. Complexity increases when these API calls need authentication.

The initial obvious thought that comes to ones mind is to create different views for each format and add respond_to block in controller. No! You don’t have to do this.

Rabl

Rabl is a gem which comes in handy when you want to represent the response in both json and xml using a single template. These are the only 2 formats currently supported.

We can enable API for an existing application or new application – the steps are same! Lets see how we

  • enable APIs without authentication.
  • enable APIs with authentication.

Enabling API access without authentication

  • Add ‘rabl’ entry to your Gemfile
  • You might need to add json/xml parser gem to your Gemfile if don’t have them already
  • Execute ‘bundle update’

Now, lets enable json & xml api response for https://localhost/tasks for getting list of tasks. Create the content file

#app/views/tasks/index.rabl

collection @tasks
attributes :id, :name, :priority, :complete_at, :task_type

In the above code we only  mention the attributes that are need to be rendered. There is no need to change tasks_controller code if you haven’t used respond_to for specifying the supported format.  Otherwise, you need insert the following code inside the respond_to block of index method of tasks_controller

#app/controllers/tasks_controller.rb#index

def index
  # some code

  respond_to do |format|
   format.json{}
   fromat.xml{}
  end
end

All set! Now, lets now test the responses for json and xml format by using curl:

curl -v -H "Accept: application/json" -H "Content-type: application/json" https://localhost/tasks.json -k

[{"task":{"name":"Inform users", "task_type":"email", "priority":1, "id":2, "completed_at":null}}, {"task": {"name":"Update Bank or Building Society", "task_type":"document", "priority":1, "id":3, "completed_at":null}}]

curl -v -H “Accept: application/xml” -H “Content-type: application/xml” https://localhost/tasks.xml -k

<?xml version="1.0" encoding="UTF-8"?>
<tasks type="array">
  <task>
    <task-type>email</task-type>
    <name>Inform users</name>
    <completed_at nil="true"></completed_at>
    <id type="integer">2</id>
    <priority type="integer">1</priority>
  </task>

  <task>
    <task-type>document</task-type>
    <name>Update Bank or Society</name>
    <completed_at nil="true"></completed_at>
    <id type="integer">3</id>
    <priority type="integer">1</priority>
  </task>
</tasks>

So with single rabl template we can serve two different formats.

Enabling API access with authentication

Let us consider devise as authentication mechanism. Enable token_authenticatable along with other required devise modules in user model and run migration

#app/models/users.rb
devise :token_authenticatable

We also need to add a migration for this

#db/migrate/20111201074702_add_token_authenticatable_to_users.rb
class AddTokenAuthenticatableToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :authentication_token, :string
  end

  def self.down
    remove_column :users, :authentication_token
  end
end

Enable/uncomment the following line in devise configuration, :auth_token is the name of the parameter be passed in the user for authentication

config.token_authentication_key = :auth_token

For sign_in and sign_up, devise takes care of most of the things. But if you want authentication_token as a response for sign_up/sign_in you need to do the following

  • Add the fields in user model ‘attr_accessor’ to be shown/emitted in the response. For example, if you want email and authentication_token to be part of your response, we need to modify the model like this

#app/models/user.rb
attr_accessible :email, :authentication_token

  •  Modify RegistrationsController create method. The following snippet shows us an example for json format sign_up request with api_key check

#app/controllers/registrations_controller#create
format.json {
  if !params[:api_key].blank? and params[:api_key] == API_KEY
    resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
    sign_in(resource_name, resource)
    current_user.reset_authentication_token!
    respond_with resource, :location => after_sign_in_path_for(resource)
  else
    render :json => {'errors'=>{'api_key' => 'Invalid'}}.to_json, :status => 401
  end
}
format.any{super}

Suppose we have a pre-registered API_KEY as ‘mprmzayb’, we can use it to generate and retrieve an authentication token!

curl -v -H “Accept: application/json” -X POST -d ‘user”:{“email”:”email@email.com”, “password”:”password”, “password_confirmation”:”password”, “accept_terms”:1}}’ https://localhost/users.json?api_key=mprmzayb -k

{"user":{"accept_terms":true, "authentication_token":"VBXdiVeCDGQD3Rxtg9qp", "email":"email@email.com", "has_email_reminder":false}}

Now, if you need the task list API requires authentication (i.e. the authenticate_user! before_filter is enabled for index method of TasksController), we need to pass the authentication_token in the request from sign_in/sign_up request.

Assuming you have the index.rabl in place as shown above, lets test this out:

curl -v -H “Accept: application/json” -H “Content-type: application/json”  https://localhost/tasks.json?auth_token=VBXdiVeCDGQD3Rxtg9qp -k

[{"task":{"name":"Inform users","task_type":"email","priority":1,"id":2,"completed_at":null}},{"task":{"name":"Update Bank or Building Society","task_type":"document","priority":1,"id":3,"completed_at":null}}]

Yeah! Devise automatically takes care of authentication, so in the following senerios appropriate error message is returned.

  • If the user tries to access tasks after logout with old token

{"error":"Invalid authentication token."}

  • If the user tries to access tasks without authentication token

{"error":"You need to sign in or register before continuing."}

Enjoy building your APIs in Rails!

Advertisement

About Sethupathi Asokan

Director and Co-founder, Josh Software Private Limited.
This entry was posted in Ruby on Rails and tagged , , . Bookmark the permalink.

10 Responses to Designing Rails API using Rabl and Devise

  1. nesquena says:

    Awesome tutorial, thanks for sharing! Glad to see you using RABL, hope its been helpful.

    • Sethupathi Asokan says:

      @nesquena, its been really helpful that is the trigger for this blog post. Thanks a lot for making this gem.

  2. Hanumakanth says:

    Nice post

  3. lanlau says:

    Great tutorial, thanks a lot !
    i think there is probably a mistake in your filename #app/views/tasks/index.rabl
    shouldn’t it be #app/views/tasks/index.json.rabl ? or #app/views/tasks/index.xml.rabl
    with your filename, the attributes line wasn’t taken into account

    • Sethupathi Asokan says:

      No, don’t have to create two rabl files for each format. This is the major point of this blog post, single rabl file (i.e., app/views/tasks/index.rabl) is sufficient enough to render both the formats based on request content-type. Remember, you need to have gems for json and xml.

  4. Ed says:

    Thanks for the tutorial. In terms of Enabling API access with authentication. Assuming i am using omniauth and devise and creating a provider similar to your other helpful post on ‘multiple applications with devise, omniauth and single sign on’, will i need to enable token_authenticatable in devise as describe in this article or it will be sufficient to just use the “uid” in the authentications model of the provider or perhaps the app_secret in the clients model of the provider.

    Will use any of that nullify the need to enable token_authenticatable in devise. I following your tutorial on ‘multiple applications with devise, omniauth and single sign on’ but will want the provider to enable api access with authentication.

    Thanks.

    • Gautam Rege says:

      @Ed,
      If you are using omniauth, once you are already signed in, you would have the oauth_token already. So, all API calls would get authenticated automatically if you append the omniauth oauth_token with your call.

      I guess you just have to remember to use the parameter as ‘oauth_token’ and not ‘auth_token’ as in the API above. (as is with OAuth 2.0 RFC)

  5. Sooriah Joel says:

    Awesome ! Thanks a lot !

  6. sunny says:

    Good Tutorials..

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 )

Connecting to %s