Authorization


Collapse Content

We just created some pages for admins but currently anyone can access it and change the data, so let's fix that now!

Authentication is the process to verify who a user is, usually with a password. Authorization refers to the rules to determine what a user can do. For example, a regular user may get access to ordinary pages, while an admin will be able to access admin-only pages.

Let's set up authorization so non-admins cannot access admin-only pages. You could use a gem to handle authorization, but when the authorization rules are simple it's quicker to just code it yourself.

Redirecting users

When non-admins visit admin-only pages, we want to redirect them to the home page. Let's create a method for this purpose on the bottom of ProductsController:

class ProductsController < ApplicationController

  #...all controller actions...

  private 

   def ensure_admin!
     unless current_user.admin?
       redirect_to root_path
     end
   end

end

This code introduces a few new things, so let's break it down:

private - add this line to the bottom of a class to mark all methods below it as private. Private methods can only be accessed from within the class itself, not outside of it. The method we're about to create (ensure_admin!) is only for internal use of the controller, and is not for external code or requests.

current_user - Devise provides a helper method current_user which returns the current signed in user.

.admin? - this method checks if the user's admin value is true. It's equivalent to checking if current_user.admin == true.

redirect_to root_path - when the user is not an admin, we call the Rails method redirect to redirect the user to a different path, such as the home page in this case.

redirect_to vs. render
It's important to realize that redirect_to creates a new request for the page you're sending the person to, and doesn't save any instance @variables from your current code that you may have assigned.

Previously, you saw how render let's you render a different template. This only renders that template, but it doesn't run any code from the controller action with that name.

Controller Filters

Now that you have a method to ensure the user is an admin, it's time to use it. Rails let's you create controller filters to run code before or after a controller action. Use the Rails method before_action to run code before a controller action, and pass it the name of the method to execute. These filters are customarily placed at the top of the controller class:

class ProductsController < ApplicationController

  before_action :ensure_admin!

  #...all controller actions

  private 

   def ensure_admin!
     unless current_user.admin?
       redirect_to root_path
     end
   end

end

Visit /sold_out from a non-admin (but signed in) account and you'll be redirected to the home page. Only an admin can now visit this page.

In fact, the filter currently applies to all actions in the controller, so non-admins won't be able to view any of the product pages! Rails let's you adjust the filter with the only option:

  before_action :ensure_admin!, only: :sold_out

This will only run the filter before the sold_out method.

There's one more issue. When someone who isn't signed-in visits /sold_out, it causes an error:

Since non-admins shouldn't be visiting this page, you could ignore this error, but it's simple to fix. In development, Rails even highlights the line with the error and displays an error message:

undefined method `admin?' for nil:NilClass

This means admin? was called on nil. In this case the user is not signed in so calling current_user returns nil, which doesn't have any method admin?. We'll see how to fix this momentarily.

Restricting access to the admin dashboard

We can use the above before_action method to ensure a user is an admin before our own controller actions. However, rails_admin doesn't provide us with an actual controller to modify, so we'll need to look at its docs to see how to restrict access. You should quickly be able to find the page on authorization and then on manual authorization, where they show the following code:

# in config/initializer/rails_admin.rb

RailsAdmin.config do |config|
  config.authorize_with do |controller|
    redirect_to main_app.root_path unless current_user.try(:admin?)
  end
end

Copy and paste the code into the file specified and your admin dashboard will now be protected from non-admins!

config/initializers/rails_admin.rb

RailsAdmin.config do |config|

  config.authorize_with do |controller|
    redirect_to main_app.root_path unless current_user.try(:admin?)
  end

  ### Popular gems integration
  #...

end

Your app won't even raise an error when someone who isn't signed in visits the admin page. The above code solves this issue with the Rails method try. try tries to call a method, but if the method doesn't exist it returns nil instead of throwing an exception.

We can use this same code in our products_controller:

def ensure_admin!
  unless current_user.try(:admin?)
    redirect_to root_path
  end
end

Optional: To avoid duplicated code, create a new method admin_user? in applications_controller.rb with the above code to check is a user is an admin. Then replace both places with a call to admin_user?

Contact Us
Sign in or email us at [email protected]