API Devise JSON authentication / registrations / sessions - ruby-on-rails

I've created a Rails 4 app using the rails-api gem. I have my application controller which inherits from ActionController::API.
My routes file:
namespace :api do
namespace :v1 do
devise_for :users
resources :friends
end
end
I have a user model which I have ran a migration to add devise attributes. The user model also has attributes which are not specific to devise, such as first_name and last_name.
I then have a token_authentication model which stores the users token.
I'd like to use the current version of Deivse for the registration / sessions / authentication of users in my app.
My question is how can I use Devise registration controllers / session controllers to accept JSON format, create a user / session and send a JSON response. I want to enforce user authentication on every request to the API, except for a User create action. I'd need to be able to add in my token code in to Devise so that on the creation of users it also created a token, and on the sessions / authentication it checked for the token.
Any help or suggestions would be greatly appreciated.
If there's any additional info I can provide to help understand my issue, please let me know.
Many thanks
Lee

I used to do like this.
First you need override the devise controller.
# Override the devise session and registration controller to do like this.
resource.generate_auth_token!
Then write your own authenticate method.
# Authenticate user.
def current_user
#current_user ||= User.where(private_token: token).first
end
def authenticate!
render json:{error:'401 Unauthorized!'},status: 401 unless current_user
end
But then i found the devise_token_auth. I think they do better.

Related

Rails Devise gem -> Authenticate a webservice call without user login

My rails application is Devise gem for user login/authentication. I need to use a webservice call to retrieve data for manipulation. Can someone suggest how this can be done without having to use front end login?
I need some way of authenticating the web service call
SOLUTION->
I ended up using
https://github.com/gonzalo-bulnes/simple_token_authentication
Another option is
https://github.com/baschtl/devise-token_authenticatable
Before, devise use to include something called token authentication, but it was inheritely insecure and it got removed. The removed code and a better alternative was published in a gist by one of devise's team members and later written into a gem, devise-token_authenticatable, by someone else.
Simply put, you will store a authentication token in the users, and ask for both their email and token on each request to see if they match, but compare it securely. It's common not to store the user in session for this request.
You can create your own session create method and override the one that comes with devise. First inside your router you would customize devise
devise_for :users, controllers: {sessions: "sessions"}
Then you can implement this with your own controller and inherit from Devise::SessionsController
class SessionsController < Devise::SessionsController
def new
super
end
def create
self.resource = AuthService.perform(auth_options) # do the custom check here with your service call
sign_in(resource_name, resource) #this will set the devise session and login the user
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
That should give you a way to have your own custom logic for creating session. Also have a look at github implementation:
https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb
Hope it helps

Get target path before authentication

I'm using Devise for user authentication in a rails application, and so certain routes are wrapped in a authenticate tag to allow access only to users who are logged in.
authenticate :user do
post '/user/orders/new', to: 'user/orders#new', as: :new_user_order
end
For example, when a user tries to create a new order when they're not logged in, they're redirected to a sign in page.
What I want is for the user to be directed to the new order page after logging in.
Currently, we're using request.referrer which redirects the user back to the page they were on before trying to create a new order. I can't seem to figure out a way of getting the path they were targeting before being sent to sign in.
Anybody know how I could do this?
Thanks
You need to provide a before filter for user authentication in the controller and not mention it in routes . Just put the following code in the controller and it should redirect to sign-up if the user isn't logged in -
before_filter :authenticate_user
You need to override Devise's after_sign_in_path_for(resource_or_scope) method, you can do it in application controller
def after_sign_in_path_for(resource_or_scope)
# new_order_path
end
Hope that helped!
You can persist the last url - in session[:bookmark] and by doing something like this -
prepend_before_filter :set_bookmark
def set_bookmark
session[:bookmark] = request.url
end
And within SessionsController, you can use value of session[:bookmark] if it exists or after_sign_in_path_for

Rails Devise guest users tokens

There is a Rails hotel list app with Devise/CanCan set up
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:show]
load_and_authorize_resource
1) What is the most elegant way to let owners edit their hotel info without registering them as users?
2) What if we create a unique token for every hotel and email something like the following link to the corresponding owner:
http://myapp.io/hotels/10010/edit?token=AAAABBBBCCCCDDD
..how to configure Devise/CanCan so they authenticate the user and allow them edit the corresponding record in the Hotels table?
Thank you in advance!
Regards,
Serge.
A quick and dirty solution would be to override the authenticate_user! method and add a check for the token:
# application_controller.rb
def authenticate_user!
if (token = params[:token] || session[:token])
#current_user = User.find_by_token token
session[:token] = token if #current_user
else
super
end
end
First you'd have to add the token column to the user table and you'd still need to create a user for them. Generate a token and email them the link with their token. When they first arrive at the site with the token in the URL, the above method will check if there's a matching record in the db and set it as the current_user and save the token to the session so that subsequent requests will still work. Don't forget to clear that session later (logout or expires).
The reason you still need to create a user record is that you don't want to start overriding your cancan logic and you need a place to store the token anyway.
There's also some gems that will add token authentication:
https://github.com/gonzalo-bulnes/simple_token_authentication
And a simple Rails built in solution:
http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html

rails: how to check admin user for setting up a route?

I need to set up a route that is only valid if the logged in user is admin (user.admin?) using devise. I'm doing this for sidekiq, but the question is valid for any similar use.
Here is my route code:
class AdminConstraint
def matches?(request)
!request.session['warden.user.user.key'].nil? and request.session['warden'].user.admin?
end
end
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
I got this code from the sidekiq wiki: https://github.com/mperham/sidekiq/wiki/Monitoring
I tried the code they posted, which didn't work so I made some modifications as I just posted. The code above doesn't work as user evaluates to nil.
What is the correct secure way to deal with this?
thanks for any help!
If you are using devise to authenticate users, what you need to do is as follows:
Inside your routes.rb file, replace this line:
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new
with this block:
authenticate :user, lambda { |u| u.has_role? :admin } do
mount Sidekiq::Web => '/sidekiq'
end
What the block does, is telling to your devise mechanism to check first if the user is authenticated (the authenticate method is a devise method), and also that the user has the admin role (which the lambda block checks), before defining the route to the sidekiq UI.
This way, only users with an admin role will be able to access this route. Other users will get routing error in development and 404 error in production.
One more thing, the has_role? method is a rolify gem which in my case I use, but you can replace it with any other method/query that checks if the user is admin.
You're not really trying to change the route depending on who the user is (I'm not even sure if it's possible in rails without a lot of hacking), you're trying to authorize a user's action.
There are several good authorization libraries. Personally I like CanCan, but there are many others.
if you are doing any type of filtering in the controller for devise you can use the helpers
class ###Controller < ApplicationController
before_filter: :authenticate_admin!
def index
end
or whichever devise model you are trying to allow access for i.e
before_filter: :authenticate_user!
before_filter: :authenticate_author!
like bdares said. you can use the CanCan gem for authorization if the helper methods in devise isn't enough
hope it helps!

Authenticate custom strategy without creating Devise user

My setup: Rails 3.0.9, Ruby 1.9.2, Devise 1.3.4, Warden 1.0.4
I'm trying to figure out if it possible to authenticate a custom strategy and not have to create a devise user in the process upon successful authentication. In my config.warden block, the authentication works fine but if I don't create a Devise user, I won't be authenticated. My ideal scenario requires me to either successfully authenticate against a 3rd party provider and sign into my app (using Devise without a corresponding Devise user record) or if I fail to authenticate, then try Devise standard login path.
Here is the devise.rb code snippet I got working but I have to create a devise user for the authentication to work, this is something I wish to avoid
config.warden do |manager|
manager.strategies.add(:custom_strategy) do
def valid?
params[:user] && params[:user][:email] && params[:user][:password]
end
def authenticate!
...perform authentication against 3rd party provider...
if successful_authentication
u = User.find_or_initialize_by_email(params[:user][:email])
if u.new_record?
u.app = 'blah'
u.save
end
success!(u)
end
end
end
manager.default_strategies(:scope => :user).unshift :custom_strategy
end
I realized the question is old but I saw it a couple of time when I was searching for a solution to similar thing so I decided to post the answer in case anyone in the future stumbles upon similar issue. Hope this will help!
I recently had to do similar thing -> had users in my database that were authenticated with some devise/warden strategies but had created another app that has to have access to some of the endpoints to my application. Basically I wanted to do a HMAC authentication.
But I didn't want to involve any user objects in that process and here is what I had to do (provided that you already have you custom strategy that authenticates incoming request without using user object)
create a fake user model that is used so that devise wont blow op. You dont have to create any database table for that
mine looked similar to below:
class Worker # no need to create a table for him
extend ActiveModel::Callbacks
extend Devise::Models
include ActiveModel::Validations
include Concerns::ObjectlessAuthenticatable
define_model_callbacks :validation
attr_accessor :id
def persisted
false
end
def initialize(id)
#id = id
end
def self.serialize_from_session(id)
self.new(id: id)
end
def self.serialize_into_session(record)
[record.id]
end
def self.http_authenticatable
false
end
end
then in devise initializer (/initializers/devise.rb) I've added separate authentication strategy like below:
...
config.warden do |manager|
manager.scope_defaults :user, :strategies => [
...strategies i was using for users
]
manager.scope_defaults :worker, :strategies => [:worker_authentication], store: false, action: 'unautenticated_worker'
manager.failure_app = CustomFailingApp
end
...
then in routes.rb I had to create a mapping for devise to use like so
devise_for :worker # you can pass some custom options here
then wherever I needed to authenticate the worker, not the user I just had to call (in the controller) authenticate_worker!
I would expect that this is against the design of devise where all actions are done using restful routes for a resource. That said, the comments in Warden's success! method say:
# Parameters:
# user - The user object to login. This object can be anything you have setup to serialize in and out of the session
So could you not change the object u to some other object that represents the user, like a plain old Hash?

Resources