2023-05-30 - Authorization with Pundit
main topics
Pundit checklist
- install gem
rails generate pundit:install- paste pundit boilerplate in
application_controller.rb rails generate pundit:policy ModelName(singular)- edit
app/policies/collective_policy.rb- show? / new? / create?
- edit? / update? / destroy?
- the index is defined in the
Scope#resolve(usuallyscope.all)
- add
authorize @controllerin the controller's actions (probably except index) Controller#indexusingpolicy_scope(Controller)
Steps explained
bundle add pundit
rails generate pundit:install
# creates the 'app/policies/applicatoin_policy.rb'
stuff to paste in app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!
include Pundit::Authorization
# Pundit: allow-list approach
after_action :verify_authorized, except: :index, unless: :skip_pundit?
after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
# Uncomment when you *really understand* Pundit!
# rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# def user_not_authorized
# flash[:alert] = "You are not authorized to perform this action."
# redirect_to(root_path)
# end
private
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
end
Run rails server and try to acces an "#index" action.
You should see Pundit::PolicyScopingNotPerformedError (if you see uninitialized constant ApplicatoinController::Pundit, you need to bundle && rails s).
rails generate pundit:policy collective
# generates 'app/policies/collective_policy.rb'
app/policies/collective_policy.rb
class CollectivePolicy < ApplicationPolicy
class Scope < Scope
end
def show?
true
end
end
In the Controller, add the authorize line. Example
def show
authorize @restaurant
end
Do the same for new and create.
For edit, update and destroy, only allow the person who created the resource.
app/policies/restaurant_policy.rb
def edit?
update?
end
def update?
record.user == user
# in my model it was collective.owner
end
def destroy?
record.user == user
end