How to Block Non Admin Users from Accessing the Admin Dashboard
In this guide we are going to build in a security component which will prevent non admin users from accessing the admin dashboard.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide we are going to build in a security component which will prevent non admin users from accessing the admin dashboard.

Right now, if you go to localhost:3000/admin/admin_users in the browser, the admin dashboard opens for all users.

To implement this feature, let's start by creating a file called admin_dashboard_spec.rb inside spec/features.

# spec/features/admin_dashboard_spec.rb

require 'rails_helper'

describe 'admin dashboard' do
  it 'can be reached successfully' do
    visit admin_root_path
    expect(page.status_code).to eq(200)
  end
end

As you can see, this test simply checks if an admin page exists. If you run rspec, it should work. As I've mentioned before these status_code tests aren't explicit since they can give confusing results. So we'll refactor this shortly, but for right now I want to establish a baseline.

The next test case will ensure that users cannot access the dashboard without signing in.

# spec/features/admin_dashboard_spec.rb

require 'rails_helper'

describe 'admin dashboard' do
  it 'can be reached successfully' do
    visit admin_root_path
    expect(page.status_code).to eq(200)
  end

  it 'does not allow users to access without being signed in' do
    visit admin_root_path
    expect(current_path).to eq(new_user_session_path)
  end
end

Now, rspec throws an error. If you see, the path it got is different from the one it expected, so we have to fix it.

large

Let's go to controllers/admin/application_controller.rb and add a before_action method to ensure that users are signed in. In this case we're going to leverage the built in authenticate_user! method provided by Devise.

# app/controllers/admin/application_controller.rb

module Admin
  class ApplicationController < Administrate::ApplicationController
    before_action :authenticate_user!
  end
end

Now the rspec tests will pass. Now let's open the browser to check this functionality, it looks like everything is working properly! When you go to /admin, you get redirected to the sign in page if you're not logged in.

Now, we are ready to build in the ability to block non admins from accessing the dashboard.

# spec/features/admin_dashboard_spec.rb

it 'cannot be reached by a non admin users' do
  user = FactoryGirl.create(:user)
  login_as(user, :scope => :user)

  visit admin_root_path

  expect(current_path).to eq(root_path)
end

In this code, we are creating a user, and if that person is a regular user, then the application should redirect him or her to the root_path and block them from the admin dashboard.

As expected rspec failed with the following error message.

large

It failed because rspec expected a different path than what it got. To fix this, go to controllers/admin/application_controller.rb and type in this code:

# app/controllers/admin/application_controller.rb

module Admin
  class ApplicationController < Administrate::ApplicationController
    before_action :authenticate_user!
    before_filter :authenticate_admin

    def authenticate_admin
      unless current_user.try(:type) == 'AdminUser'
        flash[:alert] = "You are not authorized to access this page."
        redirect_to(root_path)
      end
    end
  end
end

In the authenticate_admin method, we are saying that if the current user is not an Admin user, he or she will get an alert message and will be redirected to the root path.

If you runrspec now, everything should pass.

One thing I don't like about the code is that I'm hard-coding the value AdminUser. In the future, what if we have a SuperUser, ReadOnlyUser or just about anyone else who needs to have the authority to access this page? Obviously, the application would fail.

To avoid this pitfall, let's create a custom method that would list out all the different types of users. This will make it easier to scale in the future if we have more user types.

# app/controllers/admin/application_controller.rb

module Admin
  def self.admin_types
    ['AdminUser']
  end

  class ApplicationController < Administrate::ApplicationController
    before_action :authenticate_user!
    before_filter :authenticate_admin

    def authenticate_admin
      unless Admin.admin_types.include?(current_user.try(:type))
        flash[:alert] = "You are not authorized to access this page."
        redirect_to(root_path)
      end
    end

  end
end

Here, we are creating a class method that can be called directly, and this method returns an array of admin types. The good thing about this method is that we can easily add more user types, and this is the only place where you need to make a change.

Next we make a change in the authenticate_admin method to call our new list of admin types. Here, we are saying that if the type of current_user does not exist in the array returned by admin_types method, then flash an error message and redirect to root path.

Now, go back to admin_dashboard_spec.rb. If you notice, we don't need the first test case because we know the admin page exists. As for the second and third test case, both are negative. A rule of thumb is that if there's a negative test case, then we also need a positive one. So, let's add another test case, like this:

# spec/features/admin_dashboard_spec.rb

it 'can be reached by an admin users' do
  admin_user = FactoryGirl.create(:admin_user)
  login_as(admin_user, :scope => :user)

  visit admin_root_path

  expect(current_path).to eq(admin_root_path)
end

Here, we are creating an Admin user, and if so, we are allowing the user to access the admin homepage.

If you run rspec all of the tests should pass. If you test this in the browser you'll see that everything is working properly

Resources