Building Custom Authorization Rules in Ruby on Rails: A Practical Tutorial

Ruby on Rails is a popular web development framework that emphasizes convention over configuration. One of its critical features is the ability to implement robust authorization systems to control user access. While gems like Pundit and CanCanCan provide ready-made solutions, sometimes you need to build custom authorization rules tailored to your application’s unique requirements. This tutorial guides you through creating such rules step-by-step.

Understanding Authorization in Rails

Authorization determines what actions a user can perform within your application. Unlike authentication, which verifies identity, authorization enforces permissions. In Rails, authorization logic can be integrated at various levels, including controllers, models, or through dedicated policies.

Setting Up Your Rails Environment

Before diving into custom rules, ensure your Rails project is set up correctly. Use Rails 6 or later for optimal features. Initialize a new project with:

rails new authorization_tutorial

Navigate into your project directory and set up a basic User model:

rails generate model User name:string role:string

Run migrations with:

rails db:migrate

Creating Custom Authorization Logic

To implement custom rules, you can define methods within your User model or create a separate service object. For simplicity, we’ll add methods to the User model.

Edit app/models/user.rb to include:

class User < ApplicationRecord def can_edit?(resource) # Example: only admins can edit role == 'admin' end def can_delete?(resource) # Example: admins and moderators can delete ['admin', 'moderator'].include?(role) end end

Implementing Authorization in Controllers

Use these methods inside your controllers to enforce permissions. For example, in app/controllers/articles_controller.rb:

class ArticlesController < ApplicationController before_action :authorize_edit, only: [:edit, :update] before_action :authorize_delete, only: [:destroy] def edit # editing logic end def update # updating logic end def destroy # deletion logic end private def current_user # Mock current user; replace with actual authentication logic User.find(session[:user_id]) end def authorize_edit unless current_user.can_edit?(Article.find(params[:id])) redirect_to root_path, alert: 'You are not authorized to edit this article.' end end def authorize_delete unless current_user.can_delete?(Article.find(params[:id])) redirect_to root_path, alert: 'You are not authorized to delete this article.' end end end

Testing Your Custom Rules

To verify your authorization logic, create seed data with users of different roles:

  • Admin user with role ‘admin’
  • Moderator with role ‘moderator’
  • Regular user with role ‘user’

Log in as each user and attempt to perform actions like editing or deleting articles. Confirm that permissions are enforced according to your rules.

Extending and Maintaining Custom Rules

As your application grows, consider organizing your authorization logic into service objects or policy classes. This approach keeps controllers clean and makes testing easier. For example, create a UserPolicy class to encapsulate permissions.

Additionally, always keep your authorization logic updated with your application’s evolving requirements. Regularly review and refactor rules for clarity and security.

Conclusion

Building custom authorization rules in Ruby on Rails provides flexibility to tailor access control to your application’s needs. By defining permission methods within models and enforcing them in controllers, you can create a secure and maintainable permission system. Remember to test thoroughly and consider organizing complex rules into dedicated policy classes for better scalability.