Pundit Documentation

repository·main·Indexed 27 days ago

https://github.com/varvet/pundit

A lightweight authorization library for Ruby that uses plain Ruby classes (Policies) to manage permissions. It provides helpers for controllers and views to check if users are allowed to perform specific actions on objects, supports policy scopes for filtering collections, and includes a dedicated RSpec testing DSL.

Tokens
6.2K
Snippets
29
Records
45
Agent score
94%

What's inside Pundit

  1. Reset Pundit user context when switching users

    main

    Pundit caches the user context. When switching users in a session, you must call pundit_reset! to ensure the new user's permissions are applied correctly.

    class ApplicationController
      include Pundit::Authorization
    
      def switch_user_to(user)
        terminate_session if authenticated?
        start_new_session_for user
        pundit_reset!
      end
    end
  2. Install Pundit

    main

    Add pundit to your Gemfile using bundle add pundit. To integrate it into a Rails application, include Pundit::Authorization in your ApplicationController.

    You can also run the Pundit generator to create an ApplicationPolicy with default settings.

    bundle add pundit
    class ApplicationController < ActionController::Base
      include Pundit::Authorization
    end
    rails g pundit:install
  3. Implement Headless Policies

    main

    If you need to authorize something that doesn't have a corresponding model class (like a Dashboard), use a symbol. The policy must still accept two arguments in initialize, where the second argument will be the symbol passed to authorize.

    # app/policies/dashboard_policy.rb
    class DashboardPolicy
      attr_reader :user
    
      def initialize(user, _record)
        @user = user
      end
    
      def show?
        user.admin?
      end
    end
    
    # In controllers
    def show
      authorize :dashboard, :show?
    end
    
    # In views
    <% if policy(:dashboard).show? %>
      <%= link_to 'Dashboard', dashboard_path %>
    <% end %>
  4. Handle unauthenticated users in policies

    main

    In closed systems where only logged-in users can perform actions, you can prevent nil user errors by raising a Pundit::NotAuthorizedError in your ApplicationPolicy constructor and the Scope constructor.

    class ApplicationPolicy
      def initialize(user, record)
        raise Pundit::NotAuthorizedError, "must be logged in" unless user
        @user   = user
        @record = record
      end
    
      class Scope
        attr_reader :user, :scope
    
        def initialize(user, scope)
          raise Pundit::NotAuthorizedError, "must be logged in" unless user
          @user = user
          @scope = scope
        end
      end
    end
  5. Create a Policy class

    main

    Pundit uses plain Ruby classes to define authorization logic. By convention, these classes are placed in app/policies and named after the model they authorize, suffixed with Policy (e.g., PostPolicy for a Post model).

    Each policy class typically:

    1. Takes two arguments in initialize: a user (retrieved via current_user) and a record (the model object being authorized).
    2. Implements query methods ending in ? (e.g., update?) that return a boolean. If inheriting from the generated ApplicationPolicy, the model object is referred to as record.
    class PostPolicy < ApplicationPolicy
      def update?
        user.admin? or not record.published?
      end
    end
  6. Verify authorization and scopes

    main

    To ensure you don't forget to call authorize or policy_scope, use Pundit's verification methods in an after_action hook in your ApplicationController. These are development aids and do not provide security themselves.

    • verify_authorized: Raises an error if authorize was not called during the action.
    • verify_policy_scoped: Raises an error if policy_scope was not called.

    To bypass these checks for specific actions or conditions, use skip_authorization or skip_policy_scope.

    class ApplicationController < ActionController::Base
      include Pundit::Authorization
      after_action :verify_authorized
    end
    
    # To bypass verification conditionally:
    def show
      record = Record.find_by(attribute: "value")
      if record.present?
        authorize record
      else
        skip_authorization
      end
    end
  7. Configure RuboCop RSpec to support Pundit

    main

    To prevent rubocop-rspec from failing when it encounters the Pundit permissions construct, ensure you are using rubocop-rspec 2.0 or newer and inherit the Pundit configuration in your .rubocop.yml.

    inherit_gem:
      pundit: config/rubocop-rspec.yml
  8. Create custom error messages using I18n

    main

    You can use the query, record, and policy properties of Pundit::NotAuthorizedError to generate localized error messages via I18n.

    # In ApplicationController
    def user_not_authorized(exception)
       policy_name = exception.policy.class.to_s.underscore
    
       flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
       redirect_back_or_to(root_path)
     end
    end
    
    # In config/locales/en.yml
    en:
     pundit:
       default: 'You cannot perform this action.'
       post_policy:
         update?: 'You cannot edit this post!'
         create?: 'You cannot create posts!'
  9. Rescue Pundit::NotAuthorizedError in Rails

    main

    Pundit raises Pundit::NotAuthorizedError when authorization fails. You can rescue this in your ApplicationController to provide custom feedback or redirects.

    To globally handle these as 403 Forbidden errors, add this to application.rb:

    config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
    class ApplicationController < ActionController::Base
      include Pundit::Authorization
    
      rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
    
      private
    
      def user_not_authorized
        flash[:alert] = "You are not authorized to perform this action."
        redirect_back_or_to(root_path)
      end
    end