Pundit is a simple authorization gem for Rails applications. It allows controlling authorization through policy classes that define what users can do. Policies go in app/policies and define permissions using plain Ruby classes and methods. Pundit is popular, well-maintained, and integrates authorization checks into controllers and views. It also supports authorization scoping and works with APIs by allowing customization of the user context.
3. Authorization vs Authentication
Today we’re talking about authorization
• Authentication is about who you are
• Authorization is about what you can do
• To work, authorization requires authentication (need
to know who you are to determine what you can do)
4. Rails Authorization: A Retrospective
• 10 years ago, in the beginning of The Enlightenment,
authorization was a “roll your own” thing
• 4 years ago, CanCan shook the Rails world to the
core
• A year ago, Pundit arrives on the scene
5. Pundit
The world’s foremost authority
• Written by Elabs
• Simple, uses pure Ruby classes
• Popular and well maintained
• Second most popular Rails authorization gem
6. Pundit Setup
• Include in Gemfile
• Optionally run generator
• Include in ApplicationController
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery
after_action :verify_authorized, :except => :index
after_action :verify_policy_scoped, :only => :index
…
rails g pundit:install
gem "pundit"
7. Policies
• Authorization in Pundit is controlled by Policy classes
• Policies go in app/policies
• Policies are PORC
9. Pundit In Controllers
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
10. Pundit In Views
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
11. Pundit Scopes
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(:published => true)
end
end
end
…
12. Pundit Scopes In Controllers
def index
@posts = policy_scope(Post)
end
13. Pundit Scopes In Views
<% policy_scope(@user.posts).each do |post| %>
<p>
<% link_to post.title, post_path(post) %>
</p>
<% end %>
14. Pundit Strong Parameters 1/2
class PostPolicy < ApplicationPolicy
…
def permitted_attributes
if user.admin? || user.owner_of?(post)
[:title, :body, :tag_list]
else
[:tag_list]
end
end
…
16. Pundit with an API 1/4
custom Pundit users
class APIController < ActionController::Base
…
!
private
!
def pundit_user
current_device
end
…
17. Pundit with an API 2/4
custom Pundit users
class APIController < ActionController::Base
…
Context = Struct.new(:app, :device, :app_instance, :user)
…
private
!
def pundit_user
Context.new(
current_app, current_device,
current_app_instance, current_user
)
end
…
18. Pundit with an API 3/4
custom Pundit users
AppInstancePolicy < ApplicationPolicy
…
def update?
app_instance == current.app_instance
end
…
19. Pundit with an API 4/4
class DeliveriesController < APIController
…
def cancel
@delivery = Delivery.find(params[:id])
authorize @delivery
@delivery.cancel
!
respond_with @delivery
end
…
20. Pundit Is PORC
You can:
• Encapsulate a set of permissions into an included module
• Use alias_method to make permissions behave the same
• Inherit from a base set of permissions
• Use metaprogramming