Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement client-server API and authorisation in Ruby (Rails/Sinatra)? [closed]

I need a suggestion on how to implement "client-server" web apps in ruby. Any guides and best practices are highly appreciated. I'm interested both in Ruby ways and gems required as it is a desired platform, and in general ways and logic to implement such things.

I'm not an excellent ruby programmer or a skilled system designer with years of expirience sadly so I really need your help as I still hope this thing would shine in the end.

The current look of the application should be like this:

DB + Auth DB <-> API App <-> Other Apps:

  • DB - a database or set of databases with one DB for group of users (regions).
  • Auth DB - one more DB with personal users' data and login information, probably single even if main databases would be split between regions.
  • API App - this thing maintains all the logic around data, access control and translations probably to have one translations base for all the apps.
  • Other Apps - bunch of different applications, tied with API, this could be data providers that pokes API with some data about some user, and UI of different kinds. All of the apps could not have own storage of user-related info and work with the data only through the API.

API App: looks like the best tool to make it is Sinatra. The questions are:

  1. How to organize API in the clear way? Rails makes nice REST path settings and folders structure for models and controllers. Any tips or gems to improve API building expirience?
  2. How to maintain access? Warden isn't looks like a good option as API clients would be the web apps themselves. So I need some kind of auth token. How could it be done? Some sort of custom OAuth provider? The thing is, I don't like the idea to store and pass session cookies through the API, some sort of access token passed with each request. This also

Other Apps: mainly web-based UIs. The logical selection for this part is Rails. The main question is how to implement client-side auth check. Devise is cool, but is it possible to make it work with token, or is it more suitable tool?

like image 558
lifecoder Avatar asked Feb 01 '13 10:02

lifecoder


1 Answers

Okay, this is going to be a bit longer:

If you are already familiar with Rails, you can have a look at the Rails API gem. The project strives to remove additional cruft from Rails which is not needed for a JSON-based RESTful API.

This may sound smooth, but it has it's downsides. First and foremost, you have the readd everything that is needed for basic rails functionality, that you may be accustomed to, e.g. respond_to. This can be a bit tricky, but is pretty straightforward when you find out which rails module originally provided the functionality usually bundled in Rails within the ActionController::Base.

That being said, let me give you an example of what i did for a small API project I did last week out of curiosity:

Initial Situation

We have a mainly finished Rails App. It all works fine, but it is basically monolithic. Everything is served by the Rails Framework. Luckily for us, all the model logic is bundled into a gem called core. the application essentially lets logged in customers create products which are than searchable through an end user view.

The goal was to provide a RESTful API for this, which can handle concurrency and larger data files (i.e. CSV, XLS) a little bit more efficiently.

Introducing Rails API

The design goal let me to the Rails API gem. The basic installation works like in Rails, except the script is called rails-api, i.e.:

rails-api new jsonapi

The advantage for me here was that I could use the core from the other application, but nothing would stop me from just introducing my own models to the jsonapi application.

That being said, you can do all the standard Rails goodies, like routing, etc. It follows the same convention. then again, standard routes initially react to JSON only, which can be a bit confusing at times.

Let me give you an example of the API Controller that handles products:

class ProductsController < ApplicationController
  include ActionController::HttpAuthentication::Token
  before_filter :find_product, :except => [:create, :index]

  def index
    render :json => @products
  end

  def create
    @product = product.new params[:product]

    if @product.save
      render :json => @product, :status => :created
    else
      render :json => @product.errors, :status => :unprocessable_entity
    end
  end

  def show
    render :json => @product
  end

  def update
    if @product.update_attributes params[:product]
      render :json => @product, :status => :ok
    else
      render :json => @product.errors
    end
  end

  def destroy
    if @product.destroy
      render :json => @product, :status => :ok
    else
      render :json => {:note => I18n.t("messages.deletion_impossible")}, :status => :unprocessable_entity
    end
  end

protected
  def find_product
    @product = Product.find params[:id]
  end
end

It is nothing special. The only thing to note is the second line where ActionController::HttpAuthentication::Token is included explicitly. This is so that your aPI may be secured by HTTP Token. If you want to know more about securing an API, I suggest Ryan Bates' Guide on Railscasts.

In essential, you provide a before filter in the ApplicationController like this:

class ApplicationController < ActionController::API  
  include ActionController::HttpAuthentication::Token::ControllerMethods
  [...]
  before_filter :restrict_access
  [...]
  def restrict_access
    authenticate_or_request_with_http_token do |token, options|
      # see if key is valid.
    end
  end
end

Again, note the second line, you have to include the ControllerMethods manually, otherwise no controller will know about authenticate_or_request_with_http_token.

Extension

You may know extend the API based on the Rails conventions. It works exactly the same way, with the exception that some stuff is intentionally missing by default. I suggest adding JBuilder (Railscast), if you need more flexibility in your JSON Templates.

Great, but what about clients?

Personally, there is a lot of choice when it comes to clients. Ultimately I find that it comes down to what you like most. I can personally recommend a small node.js layer on top of the Rails API which then gets a single page application based on backbone.js in front of it. You can also try AngularJS if you like. You can also build another Rails App around it and call the API from within your controller actions.

It also depends on what platform you want to target - a native application for iOS/Android comes to mind.

The choice I made was node.js + backbone. It currently makes the most sense for me at the time and for the project. The node layer essentially holds the Token necessary to communicate with the API and the backbone application has a small library to talk with the node layer. It can however be a double edged sword, depending on how complex your API will be. For a small example this seems to be fine, but there can be a lot of code duplication just to put the calls from the backbone application through to the Rails API.

Authentication

For authentication you can make customer based API-Keys (Tokens) and then limit the controller logic to only accept data operations which are allowed with that key. You could than manage the session via the node layer. Edit: This is authorization, not authentication. Nothing actually stops you from using Authlogic with Rails API - i have not tested it, but it should work.

I confess that i have not finished this part yet - I hope others can answer this architectural question :-)

I hope I could provide some insights.

P.S.: If you want to test your API i highly recommend httpie (It's awesome!)

like image 197
Florian Avatar answered Nov 14 '22 04:11

Florian