ActionControl

Build Status Gem Version

ActionControl is a simple and secure authentication and authorization system which let you to authenticate and authorize directly in your controllers.

Compatibility

I've tested it with a Rails 4.2.5 App but I think it should work with a lot versions of Rails since the gem uses just basic stuff of Rails.

Installation

Just add the following line to the Gemfile of Rails:

gem 'action_control'

And then bundle install and you are ready to go.

Short tutorial

With ActionControl authentication and authorization is done in you controllers. To enable authentication and authorization in one of your controllers, just add a before action for authenticate! and authorize! and the user has to authenticate and authorize on every call. You probably want to control authentication and authorization for every controller action you have in your app. To enable this just add the before action to the ApplicationController.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :authenticate!, :authorize!
end

If you try to open a page you will get an ActionControl::AuthenticationNotPerformedError or ActionControl::AuthorizationNotPerformedError. This means that you have to instruct ActionControl when a user is authorized and when not. You can do this by creating a method called authenticated? and authorized? in your controller.

class DashboardController < ApplicationController
  def index
  end

  private

  def authenticated?
    return true if user_signed_in?
  end

  def authorized?
    return true if current_user.admin?
  end 
end

If the user is now signed in, he is authenticated and authorized if he is an admin, otherwise an ActionControl::NotAuthenticatedError or ActionControl::NotAuthorizedError will be raised. Now you just have to catch this error and react accordingly. Rails has the convinient rescue_from for this case.

class ApplicationController < ActionController::Base
  # ...

  rescue_from ActionControl::NotAuthenticatedError, with: :user_not_authenticated
  rescue_from ActionControl::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authenticated
    flash[:danger] = "You are not authenticated!"
    redirect_to root_path
  end

  def user_not_authorized
    flash[:danger] = "You are not authorized to call this action!"
    redirect_to root_path
  end
end

In this example above, the user will be redirected to the root with a flash message. But you can do whatever you want. For example redirect to the sign in page.

ActionControl also has a view helper methods implemented which help you to distinguish between RESTful controller actions.

The following methods are available:

  • read_action? - If the called action just read. Actions: index, show
  • write_action? - If the called action writes something. Actions: new, create, edit, update, destroy
  • change_action? - If something will be updated or destroyed. Actions: edit, update, destroy
  • create_action? - If something will be created. Actions: new, create
  • update_action? - If something will be updated. Actions: edit, update
  • destroy_action? - If something will be destroyed. Action: destroy

So you can for example do:

def authorized?
  return true if read_action?    # Everybody is authorized to call read actions

  if write_action?
    return true if admin_signed_in?     # Just admins are allowed to write something
  end
end

This is pretty much everything you have to do for basic authentication or authorization! As you can see it's quite flexible and straightforward to use.

Pass a custom error hash

You can pass an error hash to the exception and use this in your rescue method:

class ApplicationController < ActionController::Base
  before_action :authenticate!, :authorize!

  # ...

  rescue_from ActionControl::NotAuthenticatedError, with: :user_not_authenticated
  rescue_from ActionControl::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authenticated(exception)
    flash[:danger] = "You are not authenticated! Code: #{exception.error[:code]}"
    redirect_to root_path
  end

  def user_not_authorized(exception)
    flash[:danger] = "You are not authorized to call this action! Code: #{exception.error[:code]}"
    redirect_to root_path
  end

  def authenticated?(error)
    error[:code] = "ERROR"

    return true if user_signed_in?
  end

  def authorized?(error)
    error[:code] = "ERROR"

    return true if read_action?    # Everybody is authorized to call read actions

    if write_action?
      return true if admin_signed_in?       # Just admins are allowed to write something
    end
  end
end

Known Issues

The authorization is done in a simple before action. Before callbacks are done in a specific order. If you set something which is needed in the authenticate! or authorized? method you have to call the before action after the other method again.

For example if you set @user in your controller in the set_user before action and you want to use this in authorized? action you have to add the authenticate! or authorize! method after the set_user again, otherwise @user won't be available in authenticate! or authorized?.

class UsersController < ApplicationController
  before_action :set_user
  before_action :authenticate!, :authorize!

  def show
  end

  private

  def authenticated?
    return true if user_signed_in?
  end

  def authorized?
    return true if current_user == @user
  end
end

Contribution

Create pull requests on Github and help us to improve this Gem. There are some guidelines to follow:

  • Follow the conventions
  • Test all your implementations
  • Document methods aren't self-explaining (we are using YARD)

Copyright (c) 2016 Tobias Feistmantl, MIT license