Authorization with Pundit gem

Who are you?) and Authorization (are you supposed to be here?). Authentication verifies the user’s identity while authorization verifies whether that user has access rights on certain resources to perform actions on them. Two popular gems for authorization in the rails world are CanCanCan and Pundit, we at Red Panthers prefers pundit over CanCanCan we get to write Pure Ruby Objects and keep the logic for each part separate. The gem CanCanCan isolates (encourages) all authorization logic into a single class. It is a drawback when the complexity of application increases. Pundit gem provides object oriented design patterns to build our own authorization system that meets project’s requirements. It enables us to keep the models and controllers free from authorization code and allows to keep the resource logic separately. This flexibility and simplicity of Pundit gem help to use it with ease.

To start with Pundit

Add Pundit gem to your gem file and run bundle install.
gem 'pundit'
Integrate Pundit to Rails application by adding the following line to ApplicationController.
include Pundit
If you run the Pundit’s generator as below, it will generate app/policies folder which contains application_policy.rb by default.
rails g pundit:install
Then restart the Rails server. Base class policy looks like
#app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user, :record
  def initialize(user, record)
    @user = user
    @record = record
  end
  #............
  def destroy?
    false
  end
  def scope
    Pundit.policy_scope!(user, record.class)
  end
  class Scope
    attr_reader :user, :scope
    def initialize(user, scope)
      @user = user
      @scope = scope
    end
    def resolve
      scope
    end
  end
end

Create policies

Policy classes are the core of Pundit. In the app/policies folder, we can write our own policies. Each policy is a Ruby class. Each policy class should be named after a model they belong to, followed by the word Policy. For example, use CollectionPolicy for Collection model. Pundit can also be used without an associated model. Pundit uses the current_user method to get the first argument in initialize method in ApplicationPolicy. But if current_user is not the method that should be invoked by Pundit, simply define a method in your controller.
def pundit_user
  User.find_by_other_means
end
Logically, Pundit can be used outside controllers, for example, in custom services or in views. Consider the following example. In User model,
class User < ApplicationRecord  
  has_many :collections, dependent: :destroy
end
In Collection model,
class Collection < ApplicationRecord  
  belongs_to :user
end
A collection should be able to be deleted only by the user who created it. So, let’s start by creating a new file collection_policy.rb in app/policies  that will store our policies that are specific to collections. In this file, we define a class that inherits from the ApplicationPolicy class and we will integrate delete method for managing permissions for the delete action .
#app/policies/collection_policy.rb
class CollectionPolicy < ApplicationPolicy
  def destroy?
    record.user == user
  end
end
In our CollectionPolicy class we are overriding the delete? method originally declared in the ApplicationPolicy. There it simply returns false. We can override the methods in ApplicationPolicy class with our unique requirements since it’s intended to give a structure only.
record.user == user
This states that, the only user that should be able to delete a collection is the user that created it. We can refactor this into their own method since it’s a better approach if other authorized users are needed to be added in future so that changes can be made easily in a single method instead of having to make the same changes in multiple places. This is very explicit, clearly describing the intent of the program flow. Definitely, we won’t be wondering, from where these record and user attributes are coming from. In ApplicationPolicy class we can see that they are set as read only attributes representing the object that we are adding authorization to, such as collection in our app and then the user. This is an instance of Pundit providing easy access to the items that we want to add authorization. If we add another policy class like CollectionPolicy then also we will be able to use very similar code like we are working with collections. Now let’s move to CollectionsController . By using Pundit policies, a proper permission structure can be integrated to the delete action, which earlier had no protection from unauthorized HTTP requests.
class CollectionsController < ApplicationController
  protect_from_forgery
  def create
    Collections::Create.call(collection_params, current_user)
    redirect_to root_url
  end
  def destroy
    collection = Collection.find(params[:id])
    authorize(collection, :destroy?)
    Collections::Delete.call(collection, current_user)
    redirect_to root_url
  end
  private
  def collection_params
    params.require(:collection).permit(:name)
  end
end
The authorize method automatically assumes that Collection will have a corresponding CollectionPolicy class, and instantiates this class. It should call destroy? method on this instance of the policy. Passing a second argument to authorize method is optional here. It infers from the action name that it should call destroy? method on this instance of the policy. But second argument should be passed if it doesn’t match the action name.

Policy without a corresponding model

We can create ‘headless’ policies that are not tied to any specific model. Such policies can be retrieved by passing a symbol.
# app/policies/website_policy.rb
class WebsitePolicy < Struct.new(:user, :website)
  # ...
end
# In controllers
authorize :website, :show?
# In views
<% if policy(:website).show? %>
  <%= link_to 'Website', website_path %>
<% end %>
Here a model or class named Website doesn’t exist. So WebsitePolicy is retrieved by passing a symbol. This is a headless policy.

Pundit scopes

In application_policy.rb, there is a scope class defined. It implements a method called resolve for filtering. We can inherit it from a base class and implement our own resolve method. In this example a scope is setup to allow users to view websites only if they have a link through collections. Let’s consider three models: User, Website, and Collection Collection belongs to User. In WebsitePolicy,
class Scope < Scope
  def resolve
    if user.admin?
      scope.all
    else
      scope.where(:company_id => user.collections.select(:website_id))
    end
  end
end
In the websites_controller,
def index
  @websites = policy_scope(Website.includes(:company).all)
  authorize @websites
end
Now we see only the websites where we have a collection.

Exception handling

By default, Pundit raises an exception when users attempt to access that which they are not authorized to. This situation can be handled in ApplicationController.
class ApplicationController < ActionController::Base
  include Pundit
  protect_from_forgery with: :exception
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
  def user_not_authorized
    flash[:alert] = 'You are not authorized to perform this action.'
    redirect_back(fallback_location: root_path)
 end
end
We need to rescue the Pundit::NotAuthorizedError exception with a suitable method that tells how to handle it.

Why Pundit?

  • Well suited to the service oriented architecture that’s popular for large Rails applications
  • Keep controllers skinny
  • Pundit policy objects are lightweight, adding authorization logic without as much overhead as CanCanCan
  • Emphasizes object-oriented design with discrete Ruby objects providing specialized services
Therefore, as an application grows in complexity it’s always better to prefer Pundit for authorization.

References

 ]]>