AuthorizeAction

Gem Version Build Status Coverage Status Dependency Status Code Climate

Really secure and simple authorization library for your Rails, Sinatra or whatever web framework, which just works.

According to Ruby Toolbox there are at least 28 authorization libraries/frameworks available just for Rails. And yet, they all seem to be overly complex or insecure by default. At least that's why I've never used any of them in my projects. All of this is the reason I've finally decided to create my own library to solve the problem of authorization once and for all for all Ruby (web) frameworks.

Here are a few reasons, why authorize_action is better compared to all the rest:

  • Is secure by default

    • authorize_action uses a white-list approach, which means that all controller actions are forbidden by default. No more security breaches when you forget to write an authorization rule.
  • Does only one thing and does it well

    • authorize_action doesn't deal with any other thing except authorization. It does not provide any authentication (see Devise for that), roles or permissions functionality. It just checks if a user has access to a controller action or not. That's it.
  • Has a really simple API

    • authorize_action doesn't have gazillion different API methods, separate DSL or "Getting Started" wiki pages. It just has a few methods to do all the heavy lifting. And it's really easy to understand by looking at the code in your project, who has access to which controller action.
  • Is framework-agnostic

    • Although authorize_action is made to work with Rails and Sinatra by default, it has an easy way to add support for whatever framework.
  • No database/ORM dependencies

    • authorize_action doesn't force you to use any database/ORM - handle your roles/permissions however you like.
  • No external dependencies

    • authorize_action doesn't have any direct external dependencies, which makes auditing its code and upgrading it really painless.
  • Has a lightweight codebase

    • Due to the simplicity of authorize_action its codebase is really simple, clean and foolproof. Entire codebase used in production is under 100 lines. If you're not sure what exactly happens when you use authorize_action in your project then you can understand it all quickly by looking at the code.
  • Is thoroughly tested

    • authorize_action has 100% code-coverage and all tests are ran automatically prior to each release making it impossible to ship a faulty version accidentally.
  • Follows Semantic Versioning

    • Although authorize_action is a relatively new library for now, it's going to follow Semantic Versioning, which adds some additional guarantees to developers.
  • Is cryptographically signed

    • authorize_action is one of these few gems which are cryptographically signed so you can be sure that the code you're running is signed by me. In addition, I have a calculated checksum for each gem version to be extra sure.

Installation

authorize_action is cryptographically signed. To be sure the gem you install hasn’t been tampered with:

  • Verify gem checksum:
$ gem fetch authorize_action && \
    ruby -rdigest/sha2 -e "puts Digest::SHA512.new.hexdigest(File.read(Dir.glob('authorize_action-*.gem')[0]))" && \
    curl -Ls https://raw.githubusercontent.com/jarmo/authorize_action/master/checksum/\`ls authorize_action-*.gem\`.sha512
  • Add my public key (if you haven’t already) as a trusted certificate:
$ gem cert --add <(curl -Ls https://raw.github.com/jarmo/authorize_action/master/certs/jarmo.pem)
  • Add this line to your application's Gemfile:
gem 'authorize_action'
  • And then execute:

$ bundle install --trust-policy HighSecurity

  • Or install it yourself as:

$ gem install authorize_action --trust-policy HighSecurity

Usage

  • Load authorize_action gem
  • Include framework specific module into base controller
  • Define authorization rules for each controller/class
  • Call #authorize_action! method before executing action

Rails

class ApplicationController < ActionController::Base
  # Include authorize_action methods
  include AuthorizeAction::Rails

  # Check action authorization before action.
  # By default all actions are forbidden!
  before_action :authorize_action!

  protected

  # Helper method for administrator role
  def admin?
    # ...
  end
end

class PostsController < ApplicationController
  # Everyone has access to :index
  def index
    # ...
  end

  # Only post owner has access to :edit 
  def edit
    # ...
  end

  # Authorization rules have to be defined for each controller
  # action to specify the actual access rules.
  self.authorization_rules = {
    # Calling Proc object for :index action returns true
    # thus everyone will have access to that action
    index: -> { true },

    # Calling Proc object for :edit action returns true only
    # when Post owner matches current_user
    edit: -> { Post.find(params[:id]).owner == current_user },

    # Calling referenced #admin? method for :destroy action
    # returns true only for administrators
    destroy: -> :admin?
  }
end

Sinatra

require "authorize_action"

class Blog < Sinatra::Base
  # Include authorize_action methods
  include AuthorizeAction::Sinatra

  # Check action authorization before action.
  # By default all actions are forbidden.
  # Allow requests to `public/` directory if you want.
  before { authorize_action! unless request.path_info.start_with?(settings.public_folder) }

  # Everyone has access to reading posts
  get "/posts" do
    # ...
  end

  # Only post owner has access to editing post
  get "/posts/:id/edit" do
    # ...
  end

  # Only administrator can delete posts
  delete "/posts/:id" do
    # ...
  end

  # Authorization rules have to be defined for each route
  # to specify the actual access rules.
  self.authorization_rules = {
    # Calling Proc object for `GET /posts` action returns true
    # thus everyone will have access to that action
    action(:get, "/posts") => -> { true },

    # Calling Proc object for `GET /posts/:id/edit` action
    # returns true only when Post owner matches current_user
    action(:get, "/posts/:id/edit") => -> { Post.find(params[:id]).owner == current_user },

    # Calling referenced #admin? method for `DELETE /posts/:id` action
    # returns true only for administrators
    action(:delete, "/posts/:id") => :admin?
  }

  # Helper method for administrator role
  def admin?
    # ...
  end

end

Other

class BaseController
  # Include authorize_action generic methods
  include AuthorizeAction

  # Call #authorize_action! before executing any controller actions.
  before_any_action :authorize_action!

  private

  # Override AuthorizeAction#current_action_name to implement it.
  # It is expected to return a Symbol/String object with an unique
  # controller action identifier.
  def current_action_name
    # ...
  end

  # Override AuthorizeAction#forbid_action! to halt execution 
  # of controller action by rendering HTTP 403.
  def forbid_action!
    # ...
  end

  # Set authorization rules where keys are in the exact same format
  # as returned by #current_action_name
  # and values are Proc objects returning booleans.
  self.authorization_rules = {
    # ...
  }
end

Tips & Tricks

Administrator Has Access to Every Action

There is no API for giving access to administrator for every possible action. Nevertheless it can be achieved easily by just following object-oriented programming principles.

Example below is based on Rails and Devise, but the idea is the same for whatever framework:

class ApplicationController < ActionController::Base
  include AuthorizeAction::Rails

  before_action :authorize_action!

  # Override AuthorizeAction#authorize_action! to
  # give access to administrator for every action or
  # fall back to default AuthorizeAction behavior.
  def authorize_action!
    current_user.admin? || super
  end
end

Please make sure that you really-really need to do that!

Protecting Your Views

You have protected your actions with authorize_action and your models with Strong Parameters (or something similar), but what about views?

Again, there is no separate API for that, but why not use regular Ruby methods to do that?

Here's an example:

# views/posts/edit.html.erb

<% if current_user.admin? %>
  <%= link_to "Delete", @post, method: :delete %>
<% end %>

Next step would be to create some helper class or module for roles.

License

Copyright (c) Jarmo Pertman ([email protected]). See LICENSE for details.