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

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.