Rails API : Best way to implement authentication?

Ruby on-RailsRuby on-Rails-4Rails Api

Ruby on-Rails Problem Overview


I'm writing a Rails 4 app that will expose an API for a mobile app that's yet to be developed. Users will authenticate using an e-mail and password from the mobile app.

While I've found quite a bit of information on the topic. It's hard to discern what's dated or non-optimal. I've read about HTTP Basic Auth, which doesn't seem too secure, and HTTP Token-based Auth, but I'm not sure on how to couple that with regular e-mail and password authentication (I'm using Devise by the way).

I'd just like to know what's the current best practice on how to implement this, so I'll be sure to be going the right way.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

The important point, from a security perspective, is to exchange the user's email and password for a token once, and then use that token for subsequent requests. This is because:

  1. You don't want the client app to be responsible for holding onto the user's password, where a bug or attack could cause it to be leaked; and
  2. A server-issued token gives you (and your users) the ability to expire a token if necessary, e.g. to lock out a stolen device or block a misbehaving API client.

There are many ways to accomplish this with varying levels of complexity.

Here is a tutorial that is very recent and has a thorough walkthrough for creating an API in Rails with token-based authentication (not using Devise, but still relevant to understand the concepts): https://labs.kollegorna.se/blog/2015/04/build-an-api-now/

Solution 2 - Ruby on-Rails

Another option is to include the module below in your devise MODEL and add the auth_token to you table.

app/models/concerns/token_authenticable.rb
module TokenAuthenticatable
  extend ActiveSupport::Concern

  included do
    before_save :ensure_auth_token
  end

  module ClassMethods
    def find_by_token(token)
      find_by(auth_token: token)
    end
  end

  def ensure_auth_token
    self.auth_token = generate_auth_token if auth_token.blank?
  end

  private

  def generate_auth_token
    loop do
      token = Devise.friendly_token
      break token unless self.class.exists?(auth_token: token)
    end
  end
end
app/controllers/api/v1/login_controller.rb
...
 def login_user(params)
    if params[:authentication]
      @user = User.find_by(auth_token: params[:authentication])
      if @user.nil?
        render json: err('login user by token failed', ERR_USER_NOT_FOUND), status: :not_found
        event('login_user_by_auth_failed', 'token', params[:authentication])
        return
      else
        render status: :ok, json: @user
        return
      end
    else
      user = user.find_by(email: params[:email])
      if user.nil?
        event('login_user_failed_not_found', 'user_email', params[:email])
        render json: err("login user not found #{params[:email]}", ERR_USER_NOT_FOUND), status: :not_found
        return
      end
      if user.access_locked?
        event('login_user_blocked', 'user_id', user.id)
        render json: err("login user account is locked : #{user.id}", ERR_USER_LOCKED), status: :unauthorized
        return
      end
      unless user.try(:valid_password?, params[:password])
        event("login_user_password_does_not_match #{user.id}", 'user_id',  user.id)
        render json: err('login user password does not match', ERR_PASSWORD_NOT_MATCH), status: :unauthorized
        return
      end
      event('login_user_succeeded', 'user_id', user.id)
      @user= user
      if @user.save
        response.headers['authentication'] = @user.auth_token
        render status: :ok, json: @user
        return
      else
        render json: @user.errors, status: :unprocessable_entity
        return
      end
    end
  end
...

Edit: Corrected code-breaking typo

Solution 3 - Ruby on-Rails

@Roma149 this is more of a personal preference but most people who are just starting out use Devise as it is the easiest IMO. OAuth2 is a good option as well. As a more important note you can always go to The Ruby Toolbox

There is a lot of good information on gems there and they even tell you the age and the popularity of the gem. This will also allow you to differentiate between what gems the community is really geeking out on now or what has gone stale.

Remember in Ruby and Ruby On Rails it is not always what is better gem wise but what best fits your project!

Solution 4 - Ruby on-Rails

Tiddle gem provides Devise strategy for token authentication in API-only Ruby on Rails applications. Its main feature is support for multiple tokens per user.

https://github.com/adamniedzielski/tiddle

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRoma149View Question on Stackoverflow
Solution 1 - Ruby on-RailsMatt BrictsonView Answer on Stackoverflow
Solution 2 - Ruby on-RailsAntarr ByrdView Answer on Stackoverflow
Solution 3 - Ruby on-RailsThe GugaruView Answer on Stackoverflow
Solution 4 - Ruby on-RailsZakariaView Answer on Stackoverflow