Alternative API Token for Controller When Using Devise - ruby-on-rails

I have devise token_auth working fine in my rails 3.2 application. I'm just building an API and need to override the devise authentication mechanism with another token. Why? Because one user has many locations and I want to provide independent access via this token to each location without compromising the whole account.
When a location is created, a location api_token is automatically created.
In my locations controller that I'm trying to access with the new key, I have tried this:
class Api::V1::LocationsController < ApplicationController
before_filter :restrict_access, :only => :index
def index
#locations = Location.all
#page_title = "Locations"
#page_title_content = "A list of all your locations. Click to customise and view reports"
respond_to do |format|
format.json { render json: #locations }
format.xml { render xml: #locations }
end
end
private
def restrict_access
api_key = Location.find_by_api_token(params[:access_token])
head :unauthorized unless api_key
end
end
Everything routes fine however, even when I'm not logged in and don't pass the key in the url, I am permitted to see all locations.
Any suggestions how I can get this working? Also, how can I restrict the locations seen to those with access? Usually I use cancan but can't see how this might work.

With devise you need to use :token_authenticatable and override find_for_token_authentication. Here's what works in my app.
class User
devise :token_authenticatable
self.token_authentication_key = "access_token"
def self.find_for_token_authentication conditions
grant = AccessGrant.where(access_token: conditions[token_authentication_key]).
any_of({access_token_expires_at: nil}, {:access_token_expires_at.gt => Time.now}).
first
if grant
user = User.where(_id: grant.user_id).first
user.current_grant = grant if user
user
end
end
end
And after that you just call the standard authenticate_user! in a before filter
class Api::BaseController < ApplicationController
respond_to :json
# don't protect from forgery here
skip_before_filter :verify_authenticity_token
before_filter :authenticate_user!

Related

How to set up Token Authentication properly in Rails?

I am trying to make an API for a project. I followed instructions from this video - https://www.youtube.com/watch?v=lgdUqtw4weg&t=165s
Basically in the video, I make a token column for the user model. And set up the controller so I can use a post method.
But when i run the POST.
I get the error saying
{"error":"You need to sign in or sign up before continuing."}
I think Devise is interfering with the POST and sees that the user has not logged in when trying to visit non public pages.
How do I get past this and get the token ?
Here is my api controller.
class Api::V1::UserSerializedController < ApplicationController
protect_from_forgery except: :index
respond_to :json
def create
user = User.where(email: params[:email]).first
if user.valid_password?(params[:encrypted_password])
render json: user.as_json(only: [:email, :authentication_token]),status: :created
else
head(:unauthorized)
end
end
def show
end
end
You are inheriting ApplicationController and i'm guessing you have authenticate_user! before action set there.
You'd have to either change the parent class to something like ApiController (i'd prefer this)
OR
skip this action for your Api::V1::UserSerializedController

devise_token_auth - is it possible to authenticate_user! without failing the filter chain?

I have an API that needs to return results based on whether there is a signed in user or not. I'm using devise_token_auth on the rails side, and ng_token_auth on the client side (Angular).
In my controller, I would like to do something like the following:
Try to authenticate_user, but don't fail the filter chain if authentication failed.
Use current_user when creating the response payload (current_user will either contain nil or the authenticated user).
Take this code for example:
class Api::MyController < Api::ApiController
before_filter :authenticate_user!
def index
if current_user
# Create the result json using current_user
else
# Create the result json without user data
end
render json: result
end
end
Only authenticated users will pass the authenticate_user! call and get to the index method. I'd like all users to get there, both authenticated and non-authenticated.
You could try the following (not tested because I currently don't have Rails installed).
skip_before_filter :authenticate_user!, only: [:index_auth_failed]
before_filter do
authenticate_user! rescue redirect_to index_auth_failed
end
def index
# current_user exists
end
def index_auth_failed
# current_user does not exist
end

Multiple User Types - Group Authentication - Devise Token Auth

I am using the Devise Token Auth gem (https://github.com/lynndylanhurley/devise_token_auth) with multiple users, and am getting a weird bug when trying to authenticate. I create a devise_token_auth_group that lets me authenticate multiple user types. However, neither of the if or elsif conditions below are met, although the before_action :authenticate_user! seems to pass for the index action (because it doesn't render or redirect and allows the index action to run). Any ideas if I am doing this wrong or missing something?
I am signed in as a shopper and should be getting all locations when i send a request to this action. At first, I get all locations. However, after repeatedly accessing the server every 5 seconds, #locations ends up being empty because neither the if or elsif conditions are met... This leads me to believe the before action is not working properly. As a test, I tried just hitting this route using PostMan Rest Client without any access token, and somehow it returned an empty array, so I believe that is where the problem is.
devise_token_auth_group :user, contains: [:shopper, :merchant]
before_action :authenticate_user!, except: [:update]
before_action :authenticate_merchant!, only: [:update]
def index
if merchant_signed_in?
#locations = Location.where(merchant_company_id: params[:merchant_company_id])
elsif shopper_signed_in?
#locations = Location.all
end
# the #locations variable sometimes has all locations (as expected)
# but after a bunch of sequential requests, it is empty so the action renders "null"
render json: #locations, status: 200
end
For the index action, you call the :authenticate_user! helper, so, in this action, you can access to the user_signed_in? and current_user helpers, generated from your authenticate_user! call.
According to this part of the gem code, we can read that the authenticate_XXX!, XXX_signed_in and current_XXX are generated with your devise_token_auth_group as XXX, so user.
In this case, you can only access helpers generated by your devise_token_auth_group name as:
authenticate_user!
user_signed_in?
current_user
current_users
A good way to change your code is like this:
devise_token_auth_group :user, contains: [:shopper, :merchant]
before_action :authenticate_user!, except: [:update]
before_action :authenticate_merchant!, only: [:update]
def index
if current_user.is_a? Shopper
#locations = Location.where(merchant_company_id: params[:merchant_company_id])
elsif current_user.is_a? Merchant
#locations = Location.all
end
# the #locations variable sometimes has all locations (as expected)
# but after a bunch of sequential requests, it is empty so the action renders "null"
render json: #locations, status: 200
end
More documentation here

API signin to generate token using devise in rails 4

I am implementing api via rails.
I want to implement following feature but unable to figure out how?
I tried this sample app
I have user model with email, password and access_token
class UsersController < ApplicationController
def signin
c_user = User.find_by_email(params[:email])
pass = params[:password]
if c_user.password == pass
render json: c_user.access_token
end
end
private
def users_params
params.require(:user).permit(:email, :password)
end
end
If user request via api for http://localhost:3000/signin?email=t1#t.com&password=password
then it will check email and password and return access_token that user can use for future request.
I want to implement same with devise or any other gem please help me to understand it.
Thanks in advance
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' }
I think the reason you can't do that is because there is no password field as you were expected. There is only encrypted_password in the table.
And you must not save any user's password explicitly in a field like password for security reasons.
The way you can make your api work is by using valid_password? method provided by devise to authenticate the user.
if c_user.valid_password?(params[:password])
... ...
end

Rails 3 authorization with default auth

I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end

Resources