User Authentication with Grape and Devise
Solution 1:
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
Solution 2:
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