Authorize users with Exits

Authentication is the act of knowing who is it that is visiting your website (most of the time using a with a login form). Authorization, on the other hand, is the act of restricting access to authenticated people. A few weeks back I talked about Warden as an authentication platform. The next logical step is to discuss what I use as my authorization setup.

My goal was:

  1. Focus on simplicity;
  2. Explicit about permissions that is given to a user;
  3. Fine tuned restrictions in a dynamic way;
  4. Logic resides in the controllers.

If that sounds like something you'd like. I made it a gem and it's available on Github.

A gem called Exits

Like I said earlier, I wanted to focus on simplicity. Let's take an example and walk you from there.


#controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :restrict_routes!
end


#controllers/users_controller.rb
class UsersController < ActionController::Base
  allow Admin, :all
  allow User, :show, :edit, :update

  def edit
    allow! User, &allow_itself
    # @user is accessible here.
  end

  def update
    allow! User, &allow_itself
    allow! User do
      # Some other verification for @user
    end
  end

  protected

  def allow_itself
    Proc.new do
      @user = User.find params[:id].to_i
      current_user.eql? @user
    end
  end
end

If you want your app to use Exits, you need to add the helper method restrict_routes! in your before_filter/before_action. After that, it's a matter of configure your controllers to let allow user in. You have two level of authorization that are used in conjunctions. First, Exits looks at the rules for the current controller. Those are the class methods.

After it has passed that first level, you can fine tune by using allow! in your action. That method takes a block which needs to return true or false. allow! is evaluated the way it is declared, that means that the order is preserved. You can set @user in your first rule inside your action and reuse the same variable in the subsequent blocks (if there's more than one).

Permissions are wrapped in user class

The way Exits work is that it checks current_user, it then retrieves the rules associated to the current controller & the user's class. For this reason, using STL with your user model is ideal. With no efforts!

Class method allow

The method is pretty straightforward, the first argument is the class you want to allow through this controller. The second is a list of the action this user can visit. There's an alias: :all that will allow the user to any action inside the controller. Exits wants to be explicit about permissions, this is why you have to specify rules for every class. If for example, you wouldn't have set the Admin rule, the admin would not be allowed in UsersController!

For the same reason, I decided to not use a restrict(class, *actions) method. It's my belief that such a method is error prone and harder to understand. When you add a new action to your controller and want it restricted you would have to add it to the restrict method whilst with allow, a new action in your controller would be restricted by default (unless you have set :all rules to a class).

Action method allow!

When a user is granted access to a controller, you can narrow the restriction even more by adding special cases with allow!(class, &block). It will grant access if the block returns true.

You cannot use the action method alone. It has to be set together with the class method. If the user is not granted access to the controller, it doesn't matter what rules you have set in your action, it won't reach it.

current_user is what Exits expect

Right now, Exits checks for a method named current_user in ApplicationController. This is needed in order to have the gem working!

What happens if a user fails authorization?

When a user tries to access an unauthorized part of the application, Exits raises Exits::Rules::Unauthorized. This force rails to exit the current action and be rescued by Exits.

Once rescue, the default behavior is to set a flash message and to redirect to :root. You may want to customize this so there's a function you can implement in application_controller.rb


#controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :restrict_routes!

  def unauthorized(exception)
    # Handle the unauthorization exception
  end
end

Guest are not handled by Exits!

It's important for you to understand that unauthenticated user are not filtered by Exits. It's your authenticator's job to handle guests (redirecting them to a login screen for example). By not handling guest, there's a clear separation of responsibilities.

That's it!

I think I explained every bit and pieces of what Exits is and what it can do for you. If you have any question don't hesitate to let me know by reaching me out @pothibo or by raising an issue on Github.