BLOG
Rails Authorization: Pundit and CanCanCan
User authentication in any application is of top importance, especially when coding in a web app that holds personal and sensitive information. The company will, of course, want to ensure the users' personal data is not compromised - so authentication is a must. Each development language will have various ways of doing this and Ruby on Rails is no exception. The question is: how?
How do I authenticate a user in Rails?
While there are several ways, we are going to look at two of the main gems many seasoned developers use in their applications: CanCanCan and Pundit.
CanCan was an authorization library created in the early days of Rails by Ryan Bates. It gained wide adoption as a way to organize access control to model objects based on user, but then fell into disrepair in Bates' absence.
To modernize the library, a fork called CanCanCan was created. Many of the issues of compatibility with modern libraries were fixed, but the main design of the library was retained. As CanCan, or CanCanCan, was floundering and because of perceived design issues, an alternative — Pundit — was developed.
Below is a brief overview of and benefit comparison between the two libraries.
How do you use CanCanCan?
CanCanCan uses a single Ability class to manage all authorizations, which you can create with the Rails generator:
rails generate cancan:ability
This creates app/models/ability.rb which looks like this:
class Ability
include CanCan::Ability
def initialize(user)
end
end
You must then fill the initialize method with declarations of what actions can (or cannot) be performed on each model by each user type. These authorizations can be scoped to specific conditions by passing a third parameter to the can method containing a hash of model attributes and values that must be present in the record to gain the ability:
class Ability
include CanCan::Ability
def initialize(user)
can :read, Post, public: true
if user.present? # additional permissions for logged in users (they can read their own posts)
can :read, Post, user_id: user.id
if user.admin? # additional permissions for administrators
can :read, post
end
end
end
end
You can then check abilities in the view to conditionally draw certain aspects of the UI:
<% if can? :read, @post %>
<%= link_to "View", @post %>
<% end %>
In controllers, you can use the authorize! method, which will raise an exception if the user, determined by calling the current_user method, is not authorized to perform the given action.
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
authorize! :read, @post
end
end
Since an exception is thrown when access is denied, you need to catch it and respond appropriately, which is usually done in the ApplicationController:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
respond_to do |format|
format.json { head :forbidden, content_type: 'text/html' }
format.html { redirect_to main_app.root_url, notice: exception.message }
format.js { head :forbidden, content_type: 'text/html' }
end
end
end
Nothing about CanCanCan is particularly problematic, however, it's lacking in a few ways. The most obvious is that as the authorization needs of the application grow, the Ability class starts to become unwieldy and there aren't ways to organize things well.
How do you use Pundit?
Pundit was designed to be a more object-oriented approach to authorization, using plain old ruby objects. This allows developers to use all the normal code organization patterns, modules, composition, inheritance, etc. Instead of a single Ability class, Pundit starts with a policy class per-model, stored in the app/policies/ directory.
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def update?
user.admin? || !post.published?
end
end
To initialize Pundit support, you need to include it in the ApplicationController:
class ApplicationController < ActionController::Base
include Pundit
end
This gives you access to the authorize method that is similar to CanCan's authorize!:
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
end
Pundit infers both that the object of class Post will have a corresponding PostPolicy and that the update action should check the update? method of the policy. If the names don't match, you can pass them explicitly:
class PostsController < ApplicationController
def publish
@post = Post.find(params[:id])
authorize @post, :update?
@post.publish!
# ...
end
end
Checking policy in views is similar to CanCan, but you call the methods you define directly instead of passing a symbolized representation of the ability:
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
Notice this is just a plain old Ruby object. It just needs to receive a user and resource object as initialization parameters, and provide methods that check authorization. This allows trivially breaking free of ActiveRecord, as well as the constraint requiring every ability to manage only a single database table. Let's say you want to control abilities on a dashboard that is not backed directly by a model class. That's fine — you can pass any object into a policy initializer:
class DashboardPolicy < Struct.new(:user, :dashboard)
def show_super_secret?
user.id == 42
end
end
# In view
<%= if policy(:dashboard).show_super_secret? %>
<%= link_to 'Super Secret Dashboard', secret_dashboard_path %>
<% end %>
# In controller
class DashboardsController < ApplicationController
def secret_dashboard
authorize :dashboard, :show_super_secret?
# ...
end
end
Top Authentication Gem for Ruby on Rails (In Our Opinion)
Since Pundit manages to provide the same feature set as CanCan with a more object-oriented style, we choose Pundit when possible.