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.
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:
- 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
- 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/
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
@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!
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