Category guides

Authorization gems for Rails: Pundit, CanCanCan and Action Policy

Category guides · · Published 2026-10-24

A row of glass ticket barriers at a modern tram station
Ticket barriers at a tram station: who may pass, and where.

Authentication tells you who the user is. Authorization decides whether that user may edit this invoice, see that project or delete an account. Rails has no built-in authorization layer, so this is still a job for a gem, or for your own code.

Versions quoted are from RubyGems.org, October 2026.

Three ways to describe permissions

The main libraries differ less in what they can express than in where the rules live:

Pundit

Pundit (2.5.2, September 2025) is deliberately small. A policy is a plain Ruby object initialised with the user and the record:

class InvoicePolicy < ApplicationPolicy
  def update?
    user.admin? || record.owner == user
  end

  class Scope < ApplicationPolicy::Scope
    def resolve
      user.admin? ? scope.all : scope.where(owner: user)
    end
  end
end

In the controller you call authorize @invoice, which infers the policy and the action, and policy_scope(Invoice) for index pages. An after_action :verify_authorized hook raises an error if a controller action forgot to authorise, which is one of Pundit's most valuable safety nets. Policies are trivial to unit-test because they are just Ruby objects.

Strengths: very little magic, easy to read, easy to test. Limits: no built-in caching, no structured explanation of why access was denied, and some repetition across similar policies.

CanCanCan

CanCanCan (3.7.0, October 2026) is the community continuation of CanCan, the authorization plugin many developers met through RailsCasts in the Rails 3 era. Rules are declared in an Ability class:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Article, published: true
    return unless user

    can :manage, Article, author_id: user.id
    can :manage, :all if user.admin?
  end
end

Hash conditions can be turned into SQL, so Article.accessible_by(current_ability) returns only the records a user may read, and load_and_authorize_resource loads and checks records in RESTful controllers. Views use can? :update, @article.

Strengths: concise for role-based applications, and seeing all permissions in one file is useful for audits. Limits: the ability file grows large in complex domains, and rules defined with blocks cannot be converted to SQL scopes, which surprises people when accessible_by raises an error.

Action Policy

Action Policy (0.7.7, September 2026) uses policy classes like Pundit, but adds features that larger applications end up building themselves: rule aliases and default rules, pre-checks (for example, "admins may do anything"), caching of authorization results, scoping for several targets (Active Record relations, arrays, GraphQL), failure reasons with i18n messages, and instrumentation. It also provides test helpers and integrations for GraphQL-Ruby.

Strengths: structure and performance for complex permission systems; good fit for APIs. Limits: more concepts to learn; a smaller community than Pundit or CanCanCan, though the project is actively released.

Roles are a separate question

None of these libraries decides how roles are stored. A boolean admin column, an enum, a join table or a gem such as Rolify are all compatible with any of them. Keep the role model as simple as your product allows: many applications that think they need arbitrary roles actually need two or three fixed ones plus ownership checks.

When no gem is enough

For small applications with a single rule ("users can only see their own records"), a scoped query such as current_user.invoices.find(params[:id]) is the most robust form of authorization there is: a record the user does not own simply cannot be found. Add a library when the rules start to differ by action, role or record state, or when you want a guarantee, like Pundit's verify_authorized, that no controller was forgotten.

Testing authorization

Whatever you choose, test permissions at two levels. Unit tests on the policy or ability cover the matrix of users and actions cheaply; Pundit's and Action Policy's plain classes make this especially easy, and CanCanCan provides RSpec matchers such as be_able_to. Then keep a few request specs that prove the controller actually enforces the rule, since a perfect policy is useless if an action never calls it. Our testing guide covers the surrounding tools.

Old plugins you may still find

Applications that started before 2012 sometimes still carry declarative_authorization, early CanCan (the original cancan gem, whose last release was in 2013), or home-grown role plugins. CanCan code usually migrates to CanCanCan with few changes, since the DSL is the same. declarative_authorization has had no release since 2013, and its rules are generally easiest to rewrite as Pundit or Action Policy classes.

Summary

LibraryRules live inScopesBest for
PunditOne policy class per modelScope#resolveMost applications; simplicity
CanCanCanOne Ability classaccessible_by from hash rulesRole-based apps, admin panels
Action PolicyPolicy classes with aliases and pre-checksMultiple scope typesLarge apps, APIs, GraphQL

Admin frameworks have their own preferences: ActiveAdmin has adapters for both Pundit and CanCanCan, for instance. See Admin panels if that is your next decision.