Currently, my Users database has a column called "admin" with a boolean value and the default set to false. I have one admin user seeded into the database.
How do write my application so that users who are the admin can create new users, but users who are not cannot? (Also, users should be created only by the admin)
It seems like there should be a simple way to do this in devise that does not involve using some external module. So far however, I have not been able to find a satisfactory answer.
I would be more likely to mark the solution which is devise only. (One that is simply standard MVC/Rails solution a plus) However, if there really is a better way to do it that doesn't involve CanCan I may accept that too.
NOTE:
I have been searching around for a while and I've found several other stackoverflow questions that are very similar to this one, but either don't quite answer the question, or use other non-devise modules. (Or both)
To implement the authorization, use a method on the controller
Exactly as suggested by #diego.greyrobot
class UsersController < ApplicationController
before_filter :authorize_admin, only: :create
def create
# admins only
end
private
# This should probably be abstracted to ApplicationController
# as shown by diego.greyrobot
def authorize_admin
return unless !current_user.admin?
redirect_to root_path, alert: 'Admins only!'
end
end
To sidestep the Devise 'already logged in' problem, define a new route for creating users.
We will simply define a new route to handle the creation of users and then point the form to that location. This way, the form submission does not pass through the devise controller so you can feel free to use it anywhere you want in the normal Rails way.
# routes.rb
Rails.application.routes.draw do
devise_for :users
resources :users, except: :create
# Name it however you want
post 'create_user' => 'users#create', as: :create_user
end
# users/new.html.erb
# notice the url argument
<%= form_for User.new, url: create_user_path do |f| %>
# The form content
<% end %>
This seems like the simplist approach. It just requires sub-classing the devise controller. See the docs for how to do that.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_action :authenticate_user!, :redirect_unless_admin, only: [:new, :create]
skip_before_action :require_no_authentication
private
def redirect_unless_admin
unless current_user.try(:admin?)
flash[:error] = "Only admins can do that"
redirect_to root_path
end
end
def sign_up(resource_name, resource)
true
end
end
# config/routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => { :registrations => 'registrations'}
end
Explaination:
Subclass the registration controller and create the route for it.
The before_action ensures that a user is logged in, and redirects them unless they are admin, if they try to sign up.
The already logged in issue is caused by Devise's require_no_authentication method and skipping it resolves the issue.
Next is that the newly created user is automatically signed in. The sign_up helper method which does this is overridden to do prevent automatic signup.
Finally, the Welcome! You have signed up successfully. sign up flash message can be changed via editing the config/locales/devise.en.yml file, if desired.
The problem is conceptual. Devise is only an Authentication library not an Authorization library. You have to implement this separately or use CanCan. Fret not however, it is easy in your case to implement this since you only have one role.
Guard your user create/update/destroy action with a before filter:
class UsersController < ApplicationController
before_filter :authorize_admin, except [:index, :show]
def create
# user create code (can't get here if not admin)
end
end
class ApplicationController < ActionController::Base
def authorize_admin
redirect_to root_path, alert: 'Access Denied' unless current_user.admin?
end
end
With this simple approach you run a before filter on any controller action that can affect a user record by first checking if the user is an admin and kicking them out to the home page if they're not.
I had this issue when working on a Rails 6 application.
I had an admin model which I Devise used to create admins.
Here's how I fixed it:
First I generated a Devise controller for the Admin model, which created the following files:
app/controllers/admins/confirmations_controller.rb
app/controllers/admins/omniauth_callbacks_controller.rb
app/controllers/admins/passwords_controller.rb
app/controllers/admins/registrations_controller.rb
app/controllers/admins/sessions_controller.rb
app/controllers/admins/unlocks_controller.rb
Next, I modified the new and create actions of the app/controllers/admins/registrations_controller.rb this way to restrict signup to admin using Devise:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
if admin_signed_in?
super
else
redirect_to root_path
end
end
# POST /resource
def create
if admin_signed_in?
super
else
redirect_to root_path
end
end
.
.
.
end
Afterwhich, I added the skip_before_action :require_no_authentication, only: [:new, :create] action to the app/controllers/admins/registrations_controller.rb this way to allow an already existing admin to create a new admin while being logged in:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
end
You may need to also hide the Register and Forgot password buttons in the login form, to only been by logged in admins:
# app/views/admins/sessions/new.html.erb
<% if admin_signed_in? %>
<%= render "admins/shared/links" %>
<% end %>
Optionally, I added first_name and last_name parameters to the allowed parameters in the app/controllers/admins/registrations_controller.rb file, after already adding it to the admin model through a database migration:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name,
:last_name])
end
# If you have extra params to permit, append them to the sanitizer.
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:first_name,
:last_name])
end
.
.
.
end
Note: You will need to modify your app/views/admin/registrations views accordingly as well to cater for this
Additionally, you may need to create a new controller where you can create the index action for admins, say, app/controllers/admins/admins_controller.rb:
class Admins::AdminsController < ApplicationController
before_action :set_admin, only: [:show, :edit, :update, :destroy]
before_action :authenticate_admin!
# GET /admins
# GET /admins.json
def index
#admins = Admin.all
end
.
.
.
end
Note: If you need more control like managing admins by updating and deleting information of other admins from your dashboard, you may need to add other action like show, edit, update, destroy, and others to this file.
And for the routes:
## config/routes.rb
# List of Admins
get 'admins', to: 'admins/admins#index'
namespace :admins do
resources :admins
end
And then modify the path used after sign up in the app/controllers/admins/registrations_controller.rb to redirect to the admins_path, this will show you a list of all the admins that have been created:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
# The path used after sign up.
def after_sign_up_path_for(resource)
admins_path
end
# The path used after sign up for inactive accounts.
def after_inactive_sign_up_path_for(resource)
admins_path
end
end
Finally, you may need to change the notice that Devise gives when you create a user in the config/locales/devise.en.yml file, from:
Welcome, You have signed up successfully
to, say:
Account was created successfully.
That's all.
I hope this helps.
I'm aware that there are already a few Stack overflow q&a's regarding this, but they have not helped solve my issue.
I have a custom user registrations controller that inherits from devise registrations controller, i.e.:
class UsersController < Devise::RegistrationsController
I want to disable the authentication filter in the devise registrations controller. i.e disable this line:
class Devise::RegistrationsController < DeviseController
prepend_before_filter :require_no_authentication, only: [ :new, :create, :cancel ]
so that no authentication is required to edit a user.
Other stack overflow answers have suggested skipping the filter in the custom controller, i.e.
class UsersController < Devise::RegistrationsController
skip_before_filter :require_no_authentication
end
but this does not work for me. When I attempt to edit a user, a page is displayed saying:
you need to sign in or sign up before continuing.
I don't think my routing is an issue. My routes are:
devise_for :users, :skip => [:sessions, :registrations, :passwords]
devise_scope :user do
resources :user
end
Thanks and much appreciated
You were looking for something like this:
skip_before_filter :authenticate_user!, :only => [:edit]
The before filter you are disabling is not used to skip authorization. It is used to check if an existing valid session already exists. This is hinted at in the last few lines by the redirect and message. This is handy to disable if want users to be able to switch sessions without signing out or as a security measure to keep old sessions from being restored which was my use case.
# Helper for use in before_actions where no authentication is required.
#
# Example:
# before_action :require_no_authentication, only: :new
def require_no_authentication
assert_is_devise_resource!
return unless is_navigational_format?
no_input = devise_mapping.no_input_strategies
authenticated = if no_input.present?
args = no_input.dup.push scope: resource_name
warden.authenticate?(*args)
else
warden.authenticated?(resource_name)
end
if authenticated && resource = warden.user(resource_name)
flash[:alert] = I18n.t("devise.failure.already_authenticated")
redirect_to after_sign_in_path_for(resource)
end
end
applying the following:
class Users::RegistrationsController < Devise::RegistrationsController
skip_before_action :require_no_authentication
end
I've set a fake user to demo my app. The idea is that visitors, who won't be signing in, should still be able to see all the functionality that a real user would have. When a non-logged-in visitor hits the site, the app will sign in the demo user and visitors will see fake data belonging to this demo user.
With this goal in mind, I setup my application controller like this.
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_current_user
def demo_user
#demo_user ||= User.find_by_email("demo#example.com")
end
protected
def set_current_user
if current_user.nil?
sign_in(demo_user)
end
end
end
My problem is that I accidentally made it impossible for real users to sign in. Anytime a real user presses the "sign in" link, they're told that they're already signed in (as the demo user).
So clearly, what I've done is nowhere close to a "best practice." What would a smart programmer do in this situation? How do I keep my nifty automatically signed in demo user but still leave the door open to real users to sign in?
You need to add an :except to your :before_filter so that it doesn't run on the sign_in action, whatever it may be. Assuming you're using devise, that would be SessionsController#new, so it would look like:
before_filter :set_current_user, :except => :new
Note that this will skip the filter for all 'new' actions, so a more targetted way of doing it would be (again, assuming you are using Devise) to create a custom SessionsController which inherits from Devise::SessionsController and leave it blank except for:
skip_before_filter :set_current_user, :only => [:new, create]
Your set_current_user function is in a before_filter which means it runs once for every request. The current_user will be nil the first time anyone visits and so they will be signed in as the demo user. You can skip the before_filter for your sessions_controller#create action (or whatever it's called in your app. For example, if you are using Devise:
class SessionsController < Devise::SessionsController
skip_before_filter :set_current_user, :only => :create
end
Here's what I ended up with. I started off with Omnikron's solution, but I needed a little more.
This post helped: https://groups.google.com/forum/#!msg/plataformatec-devise/0WylcwjSAJY/ITDF6kFjJvwJ.
class SessionsController < Devise::SessionsController
skip_before_filter :set_current_user, only: [:new, :create]
skip_before_filter :require_no_authentication, :only => [:new, :create]
def new
if user_signed_in?
sign_out current_user
redirect_to new_user_session_path
else
super
end
end
end
Assuming that I'm not signed in.
In this case, it displays this flash notice when accessing to actions in mails_controller.
You need to sign in or sign up before continuing.
However, it won't display in communities_controller.
Why? and How can I fix?
mails_controller.rb
class MailsController < ApplicationController
before_filter :authenticate_user!
....
end
communities_controller.rb
class CommunitiesController < ApplicationController
load_and_authorize_resource :find_by => :id
before_filter :authenticate_user!
end
try moving the load_and_authorize_resource line after the before_filter. The load_and_authorize_resource line will raise an exception when it can't find the resource for member actions which may be happening before you even get to the before_filter line
I tried to find solution by google and here in SO but couldn't found...
This is the only question. It has only one answer and it's accepted but doesn't work for me... Here is my code:
class RegistrationsController < Devise::RegistrationsController
before_filter :authenticate_user!
def new
puts "Method new was called"
super
end
end
When I'm not logged in at localhost:3000/sign_up page is displayed normally and Method new was called is printed. I want controller to redirect me into sign_in page if I'm not already signed in. Of course I can check it in new method and redirect but it's not a good solution... I'm sure there is a more elegant way. I even tried to use prepend_before_filter :authenticate_user! but it doesn't work too.
EDIT
I've defined routes for this controller in routs.rb
devise_for :users, :controllers => { :sessions => "sessions", :registrations => "registrations" }
Devise::RegistrationsController has require_no_authentication before filter by default.
So it's needed to skip it:
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :require_no_authentication
before_filter :authenticate_user!
def new
puts "Method new was called"
super
end
end
skip_before_filter :require_no_authentication
before_filter :authenticate_user!
does not work any more.
Use authenticate_scope! instead.