Ror Authorization
title: Ruby on Rails Authorization — Complete Guide to Pundit & CanCanCan description: 'Learn Ruby on Rails authorization: Pundit policies, CanCanCan abilities, role-based access control, scoping records, and securing controller actions by user roles.' date: 2026-06-28 lastmod: 2026-06-28 weight: 24 tags: [backend, ror]
Rails authorization controls what authenticated users can do, implemented through dedicated authorization gems like Pundit (policy objects) or CanCanCan (ability definitions).
## What You'll Learn
By the end of this tutorial, you'll implement authorization with Pundit policies, define user roles, scope records per user permissions, secure controller actions, and handle unauthorized access.
## Real-World Use
A project management app has Admin, Manager, and Member roles. Admin can delete projects. Manager can edit. Member can only view. Pundit policies enforce these rules.
## Authorization Learning Path
```mermaid
flowchart LR
A[Authentication] --> B[Authorization]
B --> C[API]
C --> D[Testing]
D --> E[Asset Pipeline]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Pundit Setup
gem "pundit"
bundle install
rails generate pundit:install
# Creates app/policies/application_policy.rb
class ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
redirect_to root_path, alert: "You are not authorized to perform this action."
end
end
Pundit Policies
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def index?
true # Anyone can view posts
end
def show?
true
end
def create?
user.present? # Must be logged in
end
def update?
user == record.user || user.admin? # Owner or admin
end
def destroy?
user.admin? # Only admin
end
# Scope for listing
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(user: user).or(scope.where(published: true))
end
end
end
end
Using Pundit in Controllers
class PostsController < ApplicationController
def index
@posts = policy_scope(Post) # Scoped by user permissions
end
def show
@post = Post.find(params[:id])
authorize @post # Check show policy
end
def create
@post = current_user.posts.build(post_params)
authorize @post # Check create policy
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
def update
@post = Post.find(params[:id])
authorize @post # Check update policy
if @post.update(post_params)
redirect_to @post, notice: "Post updated."
else
render :edit
end
end
def destroy
@post = Post.find(params[:id])
authorize @post # Check destroy policy
@post.destroy
redirect_to posts_path, notice: "Post deleted."
end
end
CanCanCan Setup
gem "cancancan"
bundle install
rails generate cancan:ability
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # Guest user
if user.admin?
can :manage, :all # Admin can do everything
elsif user.moderator?
can :manage, Post
can :manage, Comment
can :read, :all
else
can :read, :all # Anyone can read
can :create, [Post, Comment]
can :update, Post, user_id: user.id # Own posts only
can :destroy, Post, user_id: user.id
end
end
end
Role-Based Access
# app/models/user.rb
class User < ApplicationRecord
enum role: { user: 0, moderator: 1, admin: 2 }
def admin?
role == "admin"
end
def moderator?
role == "moderator"
end
end
# Pundit policy using roles
class DashboardPolicy < ApplicationPolicy
def show?
user.admin? || user.moderator?
end
end
Common Mistakes
1. Mixing Auth and Authorization
Authentication (Devise) checks identity. Authorization (Pundit) checks permissions. Don't confuse them.
2. Not Scoping Queries
Using Post.all exposes all records. Use policy_scope(Post) to respect user permissions.
3. Skipping Authorization on "Read" Actions
Public reads may be intended, but verify. Unauthorized read access can expose sensitive data.
4. Hardcoded User Checks
User.admin? in controllers is fine, but complex logic belongs in policy objects.
5. Not Handling Unauthorized Access
Without rescue_from Pundit::NotAuthorizedError, unauthorized actions raise 500 errors. Show a friendly 403 page.
Practice Questions
1. What is the difference between authentication and authorization?
Authentication verifies identity (who you are). Authorization verifies permissions (what you can do).
2. What is Pundit?
A Ruby gem that uses policy objects for authorization. Each model has a corresponding policy class.
3. What is policy_scope in Pundit?
A method that scopes database queries based on user permissions, preventing unauthorized data access.
4. What is CanCanCan?
A popular authorization gem that uses an Ability class to define user permissions with a simple DSL.
5. Challenge: Create a Pundit policy for a Project model with admin, member, and viewer roles.
class ProjectPolicy < ApplicationPolicy
def show? true
end
def update? user.admin? || record.members.include?(user)
end
def destroy? user.admin?
end
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.joins(:members).where(members: { user_id: user.id })
end
end
end
FAQ
Mini Project: Pundit Authorization
Set up Pundit policies for Posts with role-based access.
class PostPolicy < ApplicationPolicy
def update? user == record.user || user.admin? end
def destroy? user.admin? end
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.where(user: user).or(scope.where(published: true))
end
end
end
What's Next
Ruby on Rails API Ruby on Rails Testing
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro