I have difficulties to understand and also properly implement User Authentication in APIs. In other words, I have serious problem to understand the integration of Grape API with front-end frameworks such as Backbone.js, AngularJS or Ember.js.
I'm trying to pivot all different approaches and read a lot about that, but Google returns me truly bad resources and it seems to me, like there is no really good article on this topic - Rails and User authentication with Devise and front-end frameworks.
I will describe my current pivot and I hope you can provide me some feedback on my implementation and maybe point me to the right direction.
Current implementation
I have backend Rails REST API with following Gemfile(I will purposely shorten all file code)
gem 'rails', '4.1.6'
gem 'mongoid', '~> 4.0.0'
gem 'devise'
gem 'grape'
gem 'rack-cors', :require => 'rack/cors'
My current implementation has only APIs with following Routes(routes.rb):
api_base      /api        API::Base
     GET        /:version/posts(.:format)
     GET        /:version/posts/:id(.:format)
     POST       /:version/posts(.:format)
     DELETE     /:version/posts/:id(.:format)
     POST       /:version/users/authenticate(.:format)
     POST       /:version/users/register(.:format)
     DELETE     /:version/users/logout(.:format)
I created have following model user.rb
class User
  include Mongoid::Document
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  field :email,              type: String, default: ""
  field :encrypted_password, type: String, default: ""
  field :authentication_token,  type: String
  before_save :ensure_authentication_token!
  def ensure_authentication_token!
    self.authentication_token ||= generate_authentication_token
  end
  private
  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end   
end
In my controllers I created following folder structure: controllers->api->v1 and I have created following shared module Authentication (authentication.rb)
module API
  module V1
    module Authentication
      extend ActiveSupport::Concern
      included do
        before do
           error!("401 Unauthorized", 401) unless authenticated?
         end
         helpers do
           def warden
             env['warden']
           end
           def authenticated?
             return true if warden.authenticated?
             params[:access_token] && @user = User.find_by(authentication_token: params[:access_token])
           end
           def current_user
             warden.user || @user
           end
         end
       end
     end
   end
end
So every time when I want to ensure, that my resource will be called with Authentication Token, I can simply add this by calling: include API::V1::Authentication to the Grape resource:
module API
  module V1
    class Posts < Grape::API
      include API::V1::Defaults
      include API::V1::Authentication
Now I have another Grape resource called Users(users.rb) and here I implement methods for authentication, registration and logout.(I think that I mix here apples with pears, and I should extract the login/logout process to another Grape resource - Session).
module API
  module V1
    class Users < Grape::API
      include API::V1::Defaults
      resources :users do
        desc "Authenticate user and return user object, access token"
        params do
           requires :email, :type => String, :desc => "User email"
           requires :password, :type => String, :desc => "User password"
         end
         post 'authenticate' do
           email = params[:email]
           password = params[:password]
           if email.nil? or password.nil?
             error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
             return
           end
           user = User.find_by(email: email.downcase)
           if user.nil?
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
              return
           end
           if !user.valid_password?(password)
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
              return
           else
             user.ensure_authentication_token!
             user.save
             status(201){status: 'ok', token: user.authentication_token }
           end
         end
         desc "Register user and return user object, access token"
         params do
            requires :first_name, :type => String, :desc => "First Name"
            requires :last_name, :type => String, :desc => "Last Name"
            requires :email, :type => String, :desc => "Email"
            requires :password, :type => String, :desc => "Password"
          end
          post 'register' do
            user = User.new(
              first_name: params[:first_name],
              last_name:  params[:last_name],
              password:   params[:password],
              email:      params[:email]
            )
            if user.valid?
              user.save
              return user
            else
              error!({:error_code => 404, :error_message => "Invalid email or password."}, 401)
            end
          end
          desc "Logout user and return user object, access token"
           params do
              requires :token, :type => String, :desc => "Authenticaiton Token"
            end
            delete 'logout' do
              user = User.find_by(authentication_token: params[:token])
              if !user.nil?
                user.remove_authentication_token!
                status(200)
                {
                  status: 'ok',
                  token: user.authentication_token
                }
              else
                error!({:error_code => 404, :error_message => "Invalid token."}, 401)
              end
            end
      end
    end
  end
end
I realize that I present here a ton of code and it might not make sense, but this is what I currently have and I'm able to use the authentication_token for calls against my API which are protected by module Authentication.
I feel like this solution is not good, but I really looking for easier way how to achieve user authentication through APIs. I have several questions which I listed below.
Questions
Token  which has expiration time, etc. But I think this is almost like reinventing wheel, because for this purpose I can implement OAuth2. I would like to have lighter solution.I apologize for such a long post, but I hope that more people has the same problem and it might help me to find more answers on my questions.
Add token_authenticable to devise modules (works with devise versions <=3.2)
In user.rb add :token_authenticatable to the list of devise modules, it should look something like below:
class User < ActiveRecord::Base
# ..code..
  devise :database_authenticatable,
    :token_authenticatable,
    :invitable,
    :registerable,
    :recoverable,
    :rememberable,
    :trackable,
    :validatable
  attr_accessible :name, :email, :authentication_token
  before_save :ensure_authentication_token
# ..code..
end
Generate Authentication token on your own (If devise version > 3.2)
class User < ActiveRecord::Base
# ..code..
  devise :database_authenticatable,
    :invitable,
    :registerable,
    :recoverable,
    :rememberable,
    :trackable,
    :validatable
  attr_accessible :name, :email, :authentication_token
  before_save :ensure_authentication_token
  def ensure_authentication_token
    self.authentication_token ||= generate_authentication_token
  end
  private
  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
Add migration for authentiction token
rails g migration add_auth_token_to_users
      invoke  active_record
      create    db/migrate/20141101204628_add_auth_token_to_users.rb
Edit migration file to add :authentication_token column to users
class AddAuthTokenToUsers < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.string :authentication_token
    end
    add_index  :users, :authentication_token, :unique => true
  end
  def self.down
    remove_column :users, :authentication_token
  end
end
Run migrations
rake db:migrate
Generate token for existing users
We need to call save on every instance of user that will ensure authentication token is present for each user.
User.all.each(&:save)
Secure Grape API using auth token
You need to add below code to the API::Root in-order to add token based authentication. If you are unware of API::Root then please read Building RESTful API using Grape
In below example, We are authenticating user based on two scenarios – If user is logged on to the web app then use the same session – If session is not available and auth token is passed then find user based on the token
# lib/api/root.rb
module API
  class Root < Grape::API
    prefix    'api'
    format    :json
    rescue_from :all, :backtrace => true
    error_formatter :json, API::ErrorFormatter
    before do
      error!("401 Unauthorized", 401) unless authenticated
    end
    helpers do
      def warden
        env['warden']
      end
      def authenticated
        return true if warden.authenticated?
        params[:access_token] && @user = User.find_by_authentication_token(params[:access_token])
      end
      def current_user
        warden.user || @user
      end
    end
    mount API::V1::Root
    mount API::V2::Root
  end
end
                        Although I like the question and the answer given by @MZaragoza I think it is worth noting that token_authentical has been removed from Devise for a reason! Use of the tokens are vulnerable for timing attacks. See also this post and Devise's blog Therefor I haven't upvoted @MZaragoza's answer.
In case you use your API in combination with Doorkeeper, you could do something similar, but instead of checking for the authentication_token in the User table/model you look for the token in the OauthAccessTokens table, i.e.
def authenticated
   return true if warden.authenticated?
   params[:access_token] && @user = OauthAccessToken.find_by_token(params[:access_token]).user
end
This is more safe, because that token (i.e. the actual access_token) exists only for a certain amount of time.
Note in order to be able to do this you must have a User model and OauthAccessToken model, with:
class User < ActiveRecord::Base
   has_many :oauth_access_tokens
end
class OauthAccessToken < ActiveRecord::Base
    belongs_to :user, foreign_key: 'resource_owner_id'
end
EDIT: Please also note that generally you should not include the access_token in the URL: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-bearer-16#section-2.3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With