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:
- Focus on simplicity;
- Explicit about permissions that is given to a user;
- Fine tuned restrictions in a dynamic way;
- Logic resides in the controllers.
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.
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.