This is how I emplement such mechanism in my apps:
- Generate an access_token whenever a user is created.
- Respond with that access_token whenever the user signs in.
- Require an access_token authentication for every request needed.
user.rb
class User < ActiveRecord::Base
# Use this before callback to set up User access_token.
before_save :ensure_authentication_token
# If the user has no access_token, generate one.
def ensure_authentication_token
if access_token.blank?
self.access_token = generate_access_token
end
end
private
def generate_access_token
loop do
token = Devise.friendly_token
break token unless User.where(access_token: token).first
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
private
# To make authentication mechanism more safe,
# require an access_token and a user_email.
def authenticate_user_from_token!
user_email = params[:user_email].presence
user = user_email && User.find_by_email(user_email)
# Use Devise.secure_compare to compare the access_token
# in the database with the access_token given in the params.
if user && Devise.secure_compare(user.access_token, params[:access_token])
# Passing store false, will not store the user in the session,
# so an access_token is needed for every request.
# If you want the access_token to work as a sign in token,
# you can simply remove store: false.
sign_in user, store: false
end
end
end
Then you can use this before_filter in any controller you want to protect with access_token authentication:
before_filter :authenticate_user_from_token!
You also needs to override Devise sessions controller, so it responds with a JSON holding the access_token.
users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
# Disable CSRF protection
skip_before_action :verify_authenticity_token
# Be sure to enable JSON.
respond_to :html, :json
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource) do |format|
format.json { render json: {user_email: resource.email, access_token: resource.access_token} }
end
end
end
And make sure to specify it in your routes:
routes.rb
devise_for :users, controllers: { sessions: 'users/sessions' }