Lesson Monday

Now that we’ve learned the basics of authenticating with Bcrypt and Devise, let's discuss authorization. Authentication is the process of logging in a user while authorization is the process of determining which resources a user can access.

For instance, if we only want admins to be able to add, edit and delete new products in our site, we need to ensure only admins have access to these routes. Likewise, if we have content on our site that’s only for registered users, we need to protect that content from users that aren’t logged in.

Let’s add some basic authorization to our application. Note that you can also apply these authorization techniques to Bcrypt as well.

Protecting Content in the Controller


Let’s say our website includes routes that should only be made available to users that are logged in. For instance, a user might need to be logged in to add comments to an article. However, all users should be able to view comments, regardless of whether they’re authenticated. We could add this code to our comments controller to protect only the new comment route:

comments_controller.rb
class CommentsController
  before_action :authenticate_user!, :only => [:new]
end

This code will ensure users are authenticated before they can add comments. before_action is an example of a filter. Filters are methods that are used when a controller action is called. In our case, we want the filter to run before the controller action is called so we can prohibit users from accessing a route. Check out the Rails documentation for more information on filters: Rails Documentation: Filters.

We could also create a filter that looks like this:

before_action :authenticate_user!, :except => [:index]

This allows all users to access the index route, but only authenticated users could access other routes in the comments controller.

Note that :authenticate_user! is a method provided by Devise, so if you're using Bcrypt, you'll have to create your own custom method. You'll still be able to use before_action, though.

Filters in the Application Controller

On the other hand, let’s say only the index page of our site should be available to users that aren’t logged in. Now we want our filter to apply to all controllers, not just the comments controller. It would make more sense to move our before_action to the application controller:

application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end

Since all other controllers inherit from the application controller, this code will apply to all controllers.

Filter Exceptions

We can now make exceptions where necessary, such as for our index route:

home_controller.rb
class HomeController < ApplicationController
  skip_before_action :authenticate_user!, :only => [:index]

  def index
  end

end

Here we specify that we should skip the authenticate_user! filter that lives in our application controller, but only for the index route.

Allowing Only Admins to Access Content


We now have a way to determine whether logged-in users can access content on our site. There’s only one problem. We don’t want all users to access routes that should only be available to admins. For instance, while our users can add new reviews to a commerce site, they shouldn’t be able to add new products. Here's some sample code that will protect our new and edit routes. It looks like the code from our Bcrypt lesson.

products_controller.rb
ProductsController < ApplicationController
  before_action :only => [:new, :edit] do
    redirect_to new_user_session_path unless current_user && current_user.admin
  end
end

This filter states that users should be redirected to the login page unless they’re both logged in (current_user) and an admin. If the current_user snippet isn’t included, the page will have an error when unauthenticated users try to log in because current_user will be undefined.

Protecting Content in the View


Now our routes are protected, but we may still have content in our views that should only be accessible to certain users. For example, on our index page (which is accessible to all users), we might want to add a link to add new products (which should only be accessible to admins).

We can handle this by adding a conditional to the view. Here’s an example:

index.html.erb
<% if current_user && current_user.admin %>
  <%= link_to “Add product", new_product_path %>
<% end %>

If the current_user is an admin, the link will show. If not, the link won't be on the page.

Remember, this will only hide content in the view. It won’t actually protect the route itself. If the route isn’t protected, a user can still enter the url /products/new and reach the new product page.

Examples of protecting controller content

before_action :authenticate_user!, :only => [:new]

before_action :authenticate_user!, :except => [:index]

Allowing only admins to access content

before_action :only => [:new, :edit] do
  redirect_to new_user_session_path unless current_user && current_user.admin
end

Protecting view content

<% if current_user && current_user.admin %>
  Admin content goes here!
<% end %>

Lesson 12 of 27
Last updated July 14, 2022