Doing authentication (verifying if user is sign-in or not) in Ruby on Rails is quite easy. You can write your own simple authentication in Rails or you can use devise gem on any equivalent and you are good to go.

When it comes to authorization (verifying if current_user has permission to do stuff he/she is requesting to) it’s a different topic. Yes there are several solutions out there that works well on small project (CanCanCan, Rolify, …) but once your project grows to medium to large scale then these generic solutions may become a burden.

In this article I will show you how you can do your Authorization with policy object.

Note: there is a gem pundit that has really nice plain Ruby policy object solution. But in this article we will write plain object solution for policies from scratch. If you’re looking for a gem with established convention and community I recommend checking Pundit.

Example

Let say in our application user can be:

regular user that can only do read operations of public data of clients

moderator for a particular Client that can edit client data and see private data for that client

admin which means he will be able to do anything

The code in model could look like this:

class Client < ActiveRecord::Base # ... end

class User < ActiveRecord::Base def admin? # ... end def moderator_for?(client) # .. end end

We don’t care how we retriving the information for these methods. It may be relational DB flag, it may be Rolify has_role?(:admin?) . It doesn’t matter.

Usually when developers start implementing this to the application logic they will do something like this.

# app/controllers/clients_controllers.rb class ClientsController < ApplicationController before_filter :authenticate_user! # Devise check if current user is sign_in or not (is he/she authenticated) before_filter :set_client def show end def edit if current_user.admin? || moderator_for?(@client) render :edit else render text: 'Not Authorized', status: 403 end end # ... private def set_client @client = Client.find(params[:id]) end end

And in view

# app/views/clients/show.html.erb Clients Name: <%= @client.name %> <% if current_user.admin? || current_user.moderator_for?(@client) %> Clients contact: <%= @client.email %> <% end %>

Now lets stop here and review. We have a code duplication in our controller and our view for checking current_user role in this scenario.

If business requirements change developers will have to change this in multiple places. This is dangerous as he/she may skip a file and introducing security vulnerability.

Policy Object

It’s crucial to keep your policy definitions in common place so that other developers will have to change just one file in case the requirement changes.

We will place our policy objects into folder app/policy/ .

Newer versions of Rails automaticly loads all app/ subdirectories ( ref. 1, ref. 2) Older versions of Rails will have to manually enable this directory like this: module MyAppp class Application < Rails::Application # ... config.autoload_paths << Rails.root.join('app', 'policy') # ... end end

And write our policy class:

# app/policy/client_policy.rb class ClientPolicy attr_reader :current_user, :resource def initialize(current_user:, resource:) @current_user = current_user @resource = resource end def able_to_moderate? current_user.admin? || current_user.moderator_for?(resource) end end

Our controller will look like this:

# app/controllers/clients_controllers.rb class ClientsController < ApplicationController before_filter :authenticate_user! before_filter :set_client def show end def edit if client_policy.able_to_moderate? render :edit else render text: 'Not Authorized', status: 403 end end # ... private def set_client @client = Client.find(params[:id]) end def client_policy @client_policy ||= ClientPolicy.new(current_user: current_user, resource: @client) end helper_method :client_policy end

And our view app/views/clients/show.html.erb like:

Clients Name: <%= @client.name %> <% if client_policy.able_to_moderate? %> Clients contact: <%= @client.email %> <% end %>

Now beauty of this is that you have single place where you keep your policy definitions (so if a new requirement comes it’s easy to change) and you’ve removed responsibility from controller to know policy implementation, therefore it’s easier to test.

Here is a test example:

# spec/policy/client_policy_spec.rb require 'rails_helper' RSpec.describe ClientPolicy do subject { described_class.new(current_user: user, resource: client } let(:client) { Client.new } # feel free to use factory_girl gem on this context 'when current user regular user' do let(:user) { User.new } it { expect(subject).not_to be_able_to_moderate } end context 'when current user is an admin' do let(:user) { User.new admin: true } it { expect(subject).to be_able_to_moderate } end context 'when current user is a client moderator' do let(:user) { User.new.tap { |u| u.moderable_clients << client } } it { expect(subject).to be_able_to_moderate } end context 'when current user is unrelated client moderator' do let(:user) { User.new.tap { |u| u.moderable_clients << Client.new } } it { expect(subject).not_to be_able_to_moderate } end end

more on this style of test in article expressive rspec - part 1

As we are testing various Authorization scenarios in our policy object test, all we need to test in our controller is that policy object calls the policy method that our controller action desires.

# spec/controllers/clients_controller.rb require 'rails_helper' RSpec.describe ClientsController do let(:client) { create :client } # ... describe 'GET edit' do def trigger; get :edit, id: client.id end context 'not logged in' do it 'should not access this page' do trigger expect(response.status).to eq 401 # not authenticated, e.g.: Devise restriction end end context 'logged in' do let(user) { User.new } let(:policy_double) { instance_double(ClientPolicy) } before do sign_in(user) expect(ClientPolicy) .to receive(:new).with(current_user: user, resource: client) .and_return(policy_double) expect(policy_double).to_receive(:able_to_moderate).and_return(policy_result) end context 'as authorized user' do let(:policy_result) { true } it 'should allow page render' do trigger expect(response.status).to eq 200 end end context 'as non-authorized user' do let(:policy_result) { false } it do trigger expect(response.status).to eq 403 end end end end # ... end

Scopes

Ok let say we have a requirement that on our #index page we can only list clients that have public flag or that current_user can moderate.

If we put all the logic in controller the code may look like this:

# app/controllers/clients_controllers.rb class ClientsController < ApplicationController # ... def index if current_user.admin? @clients = Client.all elsif current_user.clients.any? @clients = current_user.clients else @clients = Client.where(public: true) end end # ... end

Let’s introduce Policy Scope:

# app/policy/client_policy.rb class ClientPolicy class Scope attr_reader :current_user, :scope def initialize(current_user:, scope:) @current_user = current_user @scope = scope end def displayable return scope if current_user.admin? if current_user.clients.any? scope.where(id: current_user.clients.pluck(:id)) else scope.where(public: true) end end end # ... end

# app/controllers/clients_controllers.rb class ClientsController < ApplicationController # ... def index @clients = Client.all @clients = ClientPolicy::Scope .new(current_user: current_user, scope: @clients) .displayable # you can implement more scopes e.g. @clients.order(:created_at) # or @clients pagination # ... end # ... end

This kind of objects are called Query Policy Objects. To learn more what they are and how to test them I recommend my article Rails scopes composition and query objects

Getting complex

Here is an example of real world complex policy object:

# app/policy/client_policy.rb class ClientPolicy attr_reader :current_user, :resource def self.able_to_list?(current_user) current_user.approved? end def initialize(current_user:, resource:) @current_user = current_user @resource = resource end def able_to_view? resource.id.in?(public_client_ids) || internal_user end def able_to_update? moderator? end def able_to_delete? moderator? end def as_json { view: able_to_view?, edit: able_to_edit?, delete: able_to_delete? } end private def admin? current_user.has_role(:admin) # in this case we use Rolify style to determin admin # just to demonstrate the flexibility end def internal_user admin? || current_user.clients.any? end def moderator? current_user.admin? || current_user.moderator_for?(resource) end def public_client_ids Rails.cache.fetch('client_policy_public_clients', expires_in: 10.minutes) do Client.all.pluck(:id) end end end

There is lot happening here. First we have methods that fully represent CRUD actions on our controller able_to_view? , able_to_update? , able_to_delete? .

so our controller could look like:

class ClientsController < ApplicationController NotAuthorized = Class.new(StandardError) rescue_from NotAuthorized do |e| render json: {errors: [message: "403 Not Authorized"]}, status: 403 end def index raise NotAuthorized unless ClientPolicy.able_to_list?(current_user) @clients = Client.all @clients = ClientPolicy::Scope.new(scope: @clients, current_user: current_user) @clients # .order, .per_page, ... # ... end # ... def show raise NotAuthorized policy.able_to_view? # ... end def edit raise NotAuthorized policy.able_to_update? # ... end def update raise NotAuthorized policy.able_to_update? # ... end def delete raise NotAuthorized policy.able_to_delete? # ... end # ... end

Don’t mind that we have duplicate code in our Policy. able_to_update? and able_to_delete? are doing the same but it’s the business representation that is valuable to us. If our requirements change that only admin can delete records we change only policy class not the controller.

As for #index action there is no particular one resource we can do Authorization on. In this case we have a requirement that only activated users can list clients. So we can call class method policy where we just pass current_user .

When it comes to deciding which @clients can current_user actually see we will use Policy Scope Object as we described in section above.

But let say if the policy would say “only internal users can list clients” …you can just initialize policy without passing resource and call non-resource based methods:

# app/controllers/clients_controller.rb # ... def index index_policy = ClientPolicy.new(current_user: current_user) # no resource is passed to initialivation, this is ok in this case. raise NotAuthorized unless index_policy.able_to_list? # ... # ... # app/policy/client_policy.rb class ClientPolicy # ... able_to_list? internal_user end # ...

…you are initializing “incomplete object” but that’s ok, as you are aware that for that one interface call you don’t need resource.

Moving on to another point.

Next interesting thing is #public_client_ids method. We are using adventage of Rails model caching. Now for this particular case it may seem unecessary, but let say we are doing some really complex SQL to fetch the client ids or we call microservice:

class ClientPolicy # ... def public_client_ids Rails.cache.fetch('client_policy_public_clients', expires_in: 10.minutes) do body = HTTParty.get('http://my-micro-service.com/api/v1/public_clients.json') JSON.parse(body) end end # ... end

As you can see Policy Object can take care of external policy calls too.

Last this I want to show you is the #as_json method

Imagine you have Frontend framework that is supose to display button if given user is able to do particular action. I’ve seen many times that BE will just pass flags as user.admin==true or user.moderator_for=[1,2,3] to Frontend and developers have to replicate exactly same policy logic with FE framework.

What you can do instead is create current user endpoint where you already evaluate this logic for Frontend:

# app/controller/current_user_controller.rb class CurrentUser < ApplicationController def index roles = {} roles.merge!(client_policy_json) if client roles.merge!(some_other_roles) render json: roles end private def client_policy_json ClientPolicy .new(current_user: current_user, resource: client) .as_json end def client if params[:client_id] Client.find(params[:client_id]) end end def some_other_roles { can_display_admin_link: false } end end

GET /current_user?client_id=1234

…or you can just include this roles in same call as when you retriving client data.

The point is BE Policy objecs can really make your team life better.

Experimental - Policy Objects as Models Values Objects

Some developers may hate the fact that they need to initialize new instance of policy object in controller. If that’s so there is a different flavor of Policy Objects.

# app/model/appreciation.rb class Appreciation < ApplicationRecord::Base # ... def policy @policy ||= AppreciationPolicy.new.tap { |ap| ap.resource = self } end end # app/policy/appreciation_policy.rb class AppreciationPolicy attr_accessor :resource def can_be_destroyed?(by: ) by && resource.user == by end end # app/controllers/appreciations_controller.rb class AppreciationsController < ApplicationController # ... def destroy @appreciation = Appreciation.find(params[:id]) raise NotAuthorized unless @appreciation.policy.can_be_destroyed?(by: current_user) # ... end end

I don’t have much experience with this style as I’ve played with them just in my Ruby Rampage entry project but they feel quite ok. I like the fact that the code style is more functional and explicit.

I’m just worried about caching and memoization of related data. Where in Policy Objects it’s easy to cache anything (as you pass current user to object) with Policy Object as Value Object you need to be careful not to cache data for only one user.

Domain logic not CRUD

Ok so far it may sound like policy objects are all about CRUD and you will need policy object for every model/controller. Well that’s not true.

The only reason why I’ve chose to demonstrate policy objects from CRUD controller perspective is because it’s easier to understand them coming from other solutions like CanCanCan. In reality you may write methods in policy objects that has nothing to do with controller actions.

Policy objects are all about your domain logic not CURD:

class ArticlePolicy attr_reader :current_user, :resource def initialize(current_user:, resource:) @current_user = current_user @resource = resource end # ... def able_to_view? resource.published? || current_user.admin? end def able_to_comment? able_to_view? end end

class CommentsController < ApplicationController # ... def create @article = Article.find(:article_id) raise NotAuthorized unless ArticlePolicy.new(current_user: current_user, resource: @article).can_comment? @article.create(article_params) end # ... end

POST /articles/123/comments

You see, it may sense to create own CommentPolicy but if you don’t have enough requirements it may be just part of ArticlePolicy . When time comes you can refactor it out to own class.

Be aware to not go too overboard with sticking too many responsibilities to one policy object. The largest I ever had had maybe 7 public methods.

Update: Recently we had code requirement change that we allow document to be deleted from a controller (as previously this was not possible). Before the change policy object had method document_policy.able_to_delete_resorce? with meaning that you can delete resource associations on a document . Colleague used this method for the DocumentController deletion action. Now the code was by coincident doing the thing he needed form the policy so implementation on security level was ok. But here is the thing: Now you have document.resource.destroy if document_policy.able_to_delete_resorce? and document.destroy if document_policy.able_to_delete_resorce? Imagine a Junior developer not aware of this comes and change to new requirement that “any user” can remove document.resource in the DocumentPolicy …Now due to code design any user can remove any document. My argument was that we need a new method in document_policy object called able_to_delete? so that we would end up with: document.resource.destroy if document_policy.able_to_delete_resorce? and document.destroy if document_policy.able_to_delete? The methods inside the DocumentPolicy can be alias to each other until we decide to refactor them. The point that both methods represent different business requirement question on a policy object. Don’t think about policy objects as just logic holders. During implementation in controller always ask them a human question. In this case: “Can I delete resorce of that document ?”, “Can I delete a document?” and if there is a method matching that question in policy object implement it otherwise it’s missing and you need to implement it.

Generic rule is:

Policy Objects (when done right) don’t force your application to map a solution. They map your application requirements and your domain language. When your requirements change they must be super easy to change as well, otherwise you missed something and you are doing it wrong.

Dude Policy gem

2020-04-13 I’ve relased a gem Dude Policy which provides a way to do Policy Objects from perspective of a current_user current_account

# rails console article = Article.find(123) review = Review.find(123) current_user = User.find(432) # e.g. Devise on any authentication solution current_user.dude.able_to_edit_article?(article) # => true current_user.dude.able_to_add_article_review?(article) # => true current_user.dude.able_to_delete_review?(review) # => false

# spec/any_file_spec.rb RSpec.describe 'short demo' do let(:author_user) { User.create } let(:article) { Article.create(author: author_user) } let(:different_user) { User.create } # you write tests like this: it { expect(author_user.able_to_edit_article?(article)).to be_truthy } # or you can take advantage of native `be_` RSpec matcher that converts any questionmark ending method to matcher it { expect(author_user).to be_able_to_edit_article(article) } it { expect(different_user).not_to be_able_to_edit_article(article) } it { expect(author_user).not_to be_able_to_add_article_review(article) } it { expect(different_user).to be_able_to_add_article_review(article) } it { expect(author_user).not_to be_able_to_delete_review(article) } it { expect(different_user).to be_able_to_add_article_review(article) } end

I’m doing policy objects this way for couple of years now and I highly recommend this as a solution for Policy Objects as it solves many problems

Related articles

mine:

http://www.eq8.eu/blogs/38-rails-activerecord-relation-arel-composition-and-query-objects

http://www.eq8.eu/blogs/31-simple-authentication-for-one-user-in-rails

http://www.eq8.eu/blogs/39-expressive-tests-with-rspec-part-1-describe-your-tests-properly

http://www.eq8.eu/blogs/31-simple-authentication-for-one-user-in-rails

http://www.eq8.eu/blogs/30-pure-rspec-json-api-testing

example project using Policy Objects as model Value Objects https://github.com/crazy-monkey-woodoo-priest/open-thanks

external:

Reddit discussion on article: