Rails: Only allow admin user to create new users in Rails with Devise (No external modules)
Asked Answered
C

4

16

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)

Cupboard answered 21/7, 2014 at 21:47 Comment(0)
G
19

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 %>
Gantz answered 17/10, 2014 at 18:55 Comment(0)
N
9

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.
Nataline answered 24/3, 2016 at 20:25 Comment(1)
Thank u so much. This was the only understandable answer that helped me as I am new to ruby and devise.Zebra
C
7

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.

Canonicate answered 21/7, 2014 at 22:13 Comment(6)
And this will sidestep the whole "Already logged in" error that devise throws?Cupboard
I believe so. If not, try prepend_before_filter or prepend_before_action to make sure it runs before any devise filters.Canonicate
Hm... it's still getting mad at me for being logged in still.Cupboard
Are you sure that will work? To my understanding devise keeps track of either the session, or the user being logged on, and if it is active/logged in will not allow you to create new users. Will this make it so that I can create new users even when I am logged in?Cupboard
@diego.greyrobot thank you sir i'm confused by the UsersController - you have used and the RegistrationsController - which should be used to make sure only admins can create users?Brunel
for those reading in future: what about overrriding this method so that "already logged in" does not occur? rubydoc.info/github/plataformatec/devise/Devise/…Brunel
M
2

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.

Mucosa answered 14/9, 2020 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.