CanCanCan — Authorization and Abilities in Rails
In this tutorial, you will learn about CanCanCan. We cover key concepts, practical examples, and best practices to help you master this topic.
CanCanCan provides role-based authorization with an Ability class defining user permissions, supporting block conditions, hash conditions, resource loading, and accessible scopes.
What You'll Learn
By the end of this tutorial, you'll define abilities, load and authorize resources, implement role-based access, use hash and block conditions, and test authorization.
Why Authorization Matters
Authentication identifies users. Authorization controls what they can do. CanCanCan separates authorization rules from controllers for clean, testable permission management.
Real-World Use
A content management system defines abilities for editor, author, and admin roles. Editors can publish any content. Authors can edit only their own. Admins have full access.
Authorization Path
flowchart LR
A[Authentication] --> B[Authorization]
B --> C[Ability Class]
B --> D[Roles]
B --> E[Conditions]
B --> F[Loading]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Ability Class
Define permissions in the Ability class.
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
elsif user.editor?
can :manage, Post
can :manage, Category
can :read, :all
cannot :destroy, User
elsif user.author?
can :read, Post
can :create, Post
can :update, Post, user_id: user.id
can :destroy, Post, user_id: user.id
can :read, Comment
can :create, Comment
else
can :read, Post, published: true
can :create, User
end
end
end
Loading Resources
Automatically load and authorize resources.
class PostsController < ApplicationController
load_and_authorize_resource
def index
# @posts is already loaded and authorized
end
def show
# @post is loaded and authorized
end
def create
# @post is initialized and authorized
if @post.save
redirect_to @post, notice: "Post created."
else
render :new
end
end
end
# Custom loading
class PostsController < ApplicationController
load_resource only: [:show, :edit, :update, :destroy]
authorize_resource only: [:new, :create]
end
Hash Conditions
Define abilities with hash conditions.
class Ability
def initialize(user)
can :read, Post, published: true, deleted_at: nil
can :update, Post, user_id: user.id
can :manage, Post, organization_id: user.organization_id
# Nested hash conditions
can :read, Comment, post: { published: true, organization_id: user.organization_id }
# Array conditions
can :manage, [Post, Comment, Category], organization_id: user.organization_id
end
end
Block Conditions
Use blocks for complex authorization logic.
class Ability
def initialize(user)
can :manage, Subscription do |subscription|
subscription.user_id == user.id ||
user.admin? ||
(user.manager? && subscription.organization_id == user.organization_id)
end
can :export, Report do |report|
report.completed? && report.user_id == user.id
end
can :publish, Post do |post|
post.draft? && post.user_id == user.id && user.editor?
end
end
end
Common Mistakes
1. Defining Permissions in Controllers
Authorization rules belong in the Ability class, not scattered across controllers.
2. Not Handling Unauthorized Access
CanCanCan raises CanCan::AccessDenied. Rescue and respond with 403.
3. Overusing Block Conditions
Block conditions cannot be used with accessible_by scopes. Prefer hash conditions.
4. Forgetting to Register Abilities for New Actions
Custom controller actions need ability definitions for each action.
5. Not Testing Abilities
Authorization is critical security logic. Test ability definitions in ability specs.
Practice Questions
1. What is the Ability class?
The central class where all user permissions are defined.
2. What does load_and_authorize_resource do?
Automatically loads the resource and checks authorization before the action.
3. What is the difference between hash and block conditions?
Hash conditions can be used with accessible_by. Block conditions run in Ruby.
4. How do you define admin access to everything?
can :manage, :all in the admin role.
5. Challenge: Define abilities for a multi-tenant app.
class Ability
def initialize(user)
can :manage, Project, organization_id: user.organization_id
can :read, Task, project: { organization_id: user.organization_id }
can :manage, Task, user_id: user.id
can :manage, Organization, id: user.organization_id if user.admin?
end
end
FAQ
Mini Project: Role-Based Blog Authorization
Implement authorization for a blog with roles.
class Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
elsif user.editor?
can :manage, [Post, Category]
can :read, Comment
can :destroy, Comment, post: { user_id: user.id }
elsif user.author?
can [:create, :read], Post
can [:update, :destroy], Post, user_id: user.id
can :create, Comment
else
can :read, Post, published: true
end
end
end
What's Next
Rails API Mode Rails Serializers Rails Testing RSpec
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro