If someone could shed some light It would be greatly appreciated as I'm bit clueless on this issue. Basically I'm trying to get the devise authentication done with a JSON only rails rest api app built on top of rails-api gem.
I've already implemented the Sessions and Registrations handling as described below.
class ApplicationController < ActionController::API
include ActionController::MimeResponds
end
sessions_controller.rb
class SessionsController < Devise::SessionsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
def create
build_resource
resource = User.find_for_database_authentication(:email => params[:user][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render :json=> {:success=>true, :auth_token=>resource.authentication_token, :email=>resource.email}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user][:email].blank?
render :json=>{:success=>false, :message=>"missing login email parameter"}, :status=>422
end
def invalid_login_attempt
warden.custom_failure!
render :json=> {:success=>false, :message=>"Error with your login or password"}, :status=>401
end
end
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token, :only => :create
def create
user = User.new(params[:user])
if user.save
render :json=> {:user => [:email => user.email, :auth_token => user.authentication_token]}, :status => 201
return
else
warden.custom_failure!
render :json=> user.errors, :status=>422
end
end
end
Everything works fine so far. In my other controllers I've added this line to secure them. before_filter :authenticate_user! but I get this error when calling with auth_token. ?auth_token=xxxxxxxxxxx
undefined method authenticate_user!
controllers
class RestaurantsController < ApplicationController
before_filter :authenticate_user!
def index
#restaurants = Restaurant.all
end
def show
#restaurant = Restaurant.find(params[:id])
end
end
Still not sure what might be the issue here - I'm assuming this is happening because something is missing to work devise properly as its using ActionController::API
UPDATE
It seems the issue is with my routes.rb itself - This is how the routes are done.
require 'api_constraints'
MyApi::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1,default: true) do
devise_for :users
resources :friends
resources :locations
resources :profiles
resources :users
end
scope module: :v2, constraints: ApiConstraints.new(version: 2) do
# Future releases of the API can go here
end
end
end
Now If repeat the devise_for :users outside the scope everything started working.
require 'api_constraints'
MyApi::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1,default: true) do
devise_for :users
resources :friends
resources :locations
resources :profiles
resources :users
end
scope module: :v2, constraints: ApiConstraints.new(version: 2) do
# Future releases of the API can go here
end
end
devise_for :users
end
Does any one has an explanation why?
Step 1. Override devise controllers with custom controllers that replace redirects with JSON responses. Here's a custom SessionController that uses JSON responses .
Step 2. If an authentication error occurs, the control goes to warden which uses a failure_app which is nothing but a Rack application which sets the flash messages and renders/redirects based on the requested format. You need a custom json response with an errors key holding an array of errors instead of the default error key. So you need a custom_auth_failure_app.rb under config/initializers with this content.
Step 4. Now we have to tell Devise to use the custom failure app by adding this to config/initializers/devise.rb :
config.warden do |manager|
manager.failure_app = CustomAuthFailure
end
Step 5. Enable token_authenticatable in the devise model and add the following to config/initializers/devise.rb
config.http_authenticatable = true
config.skip_session_storage = [:http_auth, :token_auth]
Step 6. If you are using a true JSON API which doesn't use session, use a version of warden ( > 1.2.2 ) which has patches to handle a nil session.
Also read my blog post about creating a tested, documented and versioned JSON API using Rails4 + Rails-API + Devise which talks about all these steps.
For anyone having this same issue where the devise helper methods like authenticate_user are not working when using devise_token_auth and rails-api, try adding this line to app/controllers/application_controller.rb:
include DeviseTokenAuth::Concerns::SetUserByToken
When you run the devise_token_auth installation, it is supposed to automatically create this concern in the ApplicationController. This concern gives access to the helper methods like authenticate_user. Problem is, that concern doesn't get added if you're using rails-api instead of vanilla rails. So, you have to add it manually.
Not sure the root cause, but I suspect its because rails-api's ApplicationController inherits from ActionController::API, which is different from vanilla rails' ApplicationController, which inherits from ActionController::Base.
Related
I'm using Devise with Ruby on Rails.
What is the recommended way to redirect unauthenticated users to the sessions#new page if they attempt to access a page that requires authentication?
Right now I get an error that says no route matches the one they attempt to access (leading to a 404 error in production).
Just simple add this method to application_controller.rb
protected
def authenticate_user!
if user_signed_in?
super
else
redirect_to login_path, :notice => 'if you want to add a notice'
## if you want render 404 page
## render :file => File.join(Rails.root, 'public/404'), :formats => [:html], :status => 404, :layout => false
end
end
And you can call this method on before_filter another controllers you want.
e.g :
class HomesController < ApplicationController
before_filter :authenticate_user!
## if you want spesific action for require authentication
## before_filter :authenticate_user!, :only => [:action1, :action2]
end
Don't forget add login_path into routes.rb
devise_scope :user do
match '/sign-in' => "devise/sessions#new", :as => :login
end
note : I always use this way when play with devise for my apps authentication.. (rails 3.2 and rails 4.0.1)
You can do just like GeekTol wrote, or just put
before_action :authenticate_user!
in your controller.
In this case, devise uses the default authenticate_user! method, that will redirect to the "user_session_path" and use the default flash message.
It's not necessary to rewrite authenticate_user! method, unless you want to customize it.
I thought you could just add:
before_action :authenticate_user!
to each controller that required the user to be logged in.
I'm a Rails beginner but I found this in my own searches and it works well in my application.
You should refer to Devise's own How To: How To: Redirect to a specific page when the user can not be authenticated.
Another alternative I can think of is creating a routing Constraint wrapping your protected routes. You'd better stick to Devise's way, but here is an example:
#On your routes.rb
constraints(Constraints::LoginRequired) do
get '/example'
end
#Somewhere like lib/constraints/login_required.rb
module Constraints
class LoginRequired
def self.matches?(request)
#some devise code that checks if the user is logged in
end
end
end
Add this code in your config/routes.rb devise_for :users and resources :users and you can generate devise in views.
I've tried the code on Devise's github. In my application controller, i have:
after_filter :store_location
def store_location
# store last url - this is needed for post-login redirect to whatever the user last visited.
if (request.fullpath != "/users/sign_in" &&
request.fullpath != "/users/sign_up" &&
request.fullpath != "/users/password" )
session[:previous_url] = request.fullpath
puts 'stores location'
end
end
def after_update_path_for(resource)
session[:previous_url] || dashboard_path
puts 'after update'
end
When I check my server, the puts statement from the store_location method appears, but the puts statement from after_update_path_for does not. How do I get the after_update_redirect to work?
Here is what devise says to do, but it isn't working:
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update
From the documentation:
(Object) after_update_path_for(resource) (protected)
The default url to be used after updating a resource. You need to overwrite this method in your own RegistrationsController.
So creating your ow RegistrationsController is correct. Here is a simpler solution though:
after_update_path_for calls signed_in_root_path(resource) which looks a home #{scope}_root_path. Scope here is often user (but if not you probably know what it is). In the case of 'user', implementing user_root_path in your application controller, returning your dashboard_url, should work.
def user_root_path
dashboard_url
end
Although it seemed a bit hackish to me at first, I believe it is quite 'ok'; the root path for the user-scope is could indeed be the dashboard page.
Here's how i solved the problem:
class RegistrationsController < Devise::RegistrationsController
protected
def after_update_path_for(resource)
puts 'this is happening yoyo mama'
flash[:notice] = "Account succesfully updated"
edit_user_registration_path
end
end
routes:
devise_for :users, :controllers => { :registrations => :registrations }
The only problem is that this will only do the redirect if changing the password is successful. If not, the redirect does not happen. Does anyone know how to make it so the redirect will also happen if there are errors?
As per the Devise docs, override the default and add the route. There's no need to set the flash message unless of course you want to change that as well.
# Example subclass/override (registrations_controller.rb)
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_update_path_for(resource)
user_path(resource)
end
end
# Example routing config (in routes.rb):
devise_for :users, :controllers => { :registrations => :registrations }
respond_with resource, :location => after_update_path_for(resource) is the code which set the redirection path after update. To change the default redirect, override following method in your application controller by adding the following code
def after_update_path_for(resource_or_scope)
dashboard_url
end
Overriding the route in the ApplicationController also did not work for me, but adding it to the Users::RegistrationsController worked.
For example,
class Users::RegistrationsController < Devise::RegistrationsController
def after_update_path_for(resource)
current_user
end
on a related note, the after_sign_in_path can be added to the SessionsController
class Users::SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
current_user
end
my routes look like this:
devise_for :users, controllers: {
confirmations: "users/confirmations",
passwords: "users/passwords",
registrations: "users/registrations",
sessions: "users/sessions",
unlocks: "users/unlocks",
}
i have a rails 3.2.8 application that uses devise 2.0.0 for authentication i integrated refinerycms with this application it is integrated successfully now problem is when user hit
localhost/refinery it takes me to refinery login page but after
successfull login it redirects back to my application home page rather
than refinerycms dashboard page.
So i want to know how to redirect to the
refinery dashboard after refinery successfull login procedure.
I have route mount Refinery::Core::Engine,
:at => '/' root to:'projects#index'
etc
Create a custom devise controller that redirects the user to the place you want once they are logged in:
Create the Custom Controller: controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
before_filter :authenticate_user!, :only => [:destroy]
def new
super
end
def create
#Can edit this for custom redirect
super
end
def destroy
super
end
protected
def stub_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def after_sign_in_path_for(resource)
#Can edit this for custom redirect
super
end
def after_sign_out_path_for(resource)
super
end
private
end
then tell devise to use that controller in your routes.rb:
devise_for :users, :controllers => {:sessions => "users/sessions"}
I need to override Devise sessions controller during the login process (Rails 3.0.9, Ruby 1.9.2, Devise 1.3.4), I tried this without any effect
class SessionsController < Devise::SessionsController
# GET /resource/sign_in
def new
resource = build_resource
clean_up_passwords(resource)
respond_with_navigational(resource, stub_options(resource)){ render_with_scope :new }
end
end
Ideas?
EDIT
As indicated in the answer, I also need to change the route. In addition, I also need to copy the views. It's better explained here
http://presentations.royvandewater.com/authentication-with-devise.html#8
My custom strategy:
devise.rb
config.warden do |manager|
manager.strategies.add(:custom_strategy) do
def authenticate!
... authenticate against 3rd party API...
if res.body =~ /success/
u = User.find_or_initialize_by_email(params[:user][:email])
if u.new_record?
u.save
end
success!(u)
end
end
end
Have you altered your route to use your new controller?
/config/routes.rb
devise_for :users, :controllers => {:sessions => "sessions"}
I'm going to create a multi user app, so, I will have a admin user that will have permission to create new ones.
I've created the UsersControllerbut when trying to create a new user, being already signed in, I'm getting redirect to root_path with an error message that says "You are already signed in".
So, what should I do to make this possible?
Found.
I have just to removed the registerable module from devise and it works.
In a controller method can't you just go:
def create_user
#user = User.new(:email => params[:email], :password => params[:password])
#user.save
...
end
This is how I am doing it in 2015
# in your terminal
rails g controller Registrations
Registrations controller should look like this,
# registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :require_no_authentication, only: [:new]
def new
super
end
end
The important line is the skip_before_filter...
That will disable the requirement that there be no user logged in.
The routes for the controller looks like this,
# routes.rb
devise_for :users,
controllers: {:registrations => "registrations"}
That will tell devise to use your custom registrations controller
Finally, setting up a custom route to that action:
# routes.rb
as :user do
get "/register", to: "registrations#new", as: "register"
end
There is another solution.
You must override registration controller and remove action (or actions) from prepend_before_filter.
DeviseRegistrationController source is here.
You can see:
prepend_before_filter :require_no_authentication, only: [:new, :create, :cancel]
It jumps into require_no_authentication before create method. If you want create new user while you are logged in, just remove :create from array.
I added in the Registrations Controller:
class RegistrationsController < Devise::RegistrationsController
...
skip_before_action :require_no_authentication, only: [:new, :create]
...
end
And it worked for me. I can now go and create a new user.
You could either override the default devise controller and add in your custom logic, or, It would probably be easier however to make a new (Admin) controller and simply create a user using one of it's actions.
#user = User.create!(:name => params[:foo], :email => params[:bar])
redirect_to #user
Devise has loads of guides on how to customise it's behaviour here: https://github.com/plataformatec/devise/wiki/_pages
This one in particular may be of interest to you: https://github.com/plataformatec/devise/wiki/How-To:-Manage-Users-with-an-Admin-Role-(CanCan-method) But make sure to have a look over the rest of the articles, there's a lot of them.
in case someone still looking for help, because it tooks a while for this to work , no clear answers
in your controller
class UsersController < ApplicationController
def new
#user = User.new
end
def add_user
#user = User.new(user_params)
if #user.save!
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
in your routes :
get 'employees', to: 'users#new'
post 'employees', to: 'users#add_user'
and finally a form like this:
<%= form_for User.new , :url => {:action => "add_user"} do |user| %>
<%=user.email_field :email%>
<%=user.password_field :password%>
<%=user.password_field :password_confirmation%>
<%=user.submit 'add'%>
<%end%>
#Mourkeer +1
For simple_form 4.2.0 take #Mourkeer code and replace route.rb by:
# route.rb
devise_for :users, path_names: { registration: "registrations" }