Skip to content

Ror Authorization

DodaTech 4 min read

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

Should I use Pundit or CanCanCan?

Pundit is simpler and more explicit (one policy per model). CanCanCan's Ability file is concise but can become complex.

Can I use both Devise and Pundit?

Yes. They work together perfectly. Devise handles auth, Pundit handles authorization.

How do I test Pundit policies?

Use the pundit-matchers gem. Test each policy method with different user roles.

What is strong parameters vs authorization?

Strong parameters whitelist attributes. Authorization controls which users can perform actions.

Can I have per-record permissions?

Yes. Pundit checks record ownership: user == record.user. CanCanCan: can :update, Post, user_id: user.id.

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