Ruby on Rails
AuthenticationWithAbstractApplicationController

This article is part of the confusing world of Authentication in Rails. Feel free to get lost in a gazillion of nearly useless and/or outdated Wikipages.

Setup the abstract controller

In application.rb:

class ApplicationController < ActionController::Base
  before_filter :authorize

  protected
    # Override in controller classes that should require authentication
    def secure?
      false
    end

  private
    def authorize
      if secure? && session["person"].nil?
        session["return_to"] = request.request_uri
        redirect_to :controller => "auth" 
        return false
      end
    end
end

Define #secure? per controller

Completely secure controller example

class WeblogController < ApplicationController
  def index
    # show secret stuff
  end

  protected
    def secure?
      true
    end
end

Partly secure controller example

class SignupController < ApplicationController
  def index
    # show public stuff
  end

  def settings
    # show secret stuff
  end

  protected
    def secure?
      action_name == "settings" 
    end
end

variation

Multiple protected pages, but not fully protected controllers.

...
protected
def secure?
     ["onesecretpage", "secondsecretpage"].include?(action_name)
end
...

COMMENT: One small warning here, if I have an action that is not protected, that calls an action that is protected this will not protect the second action. For example if I have something like

...

def index
    list
    render :action => 'list'
end

def list
    #some listing mechanism
end

def add
    #some add mechanism
end

def show
    #some show mechanism
end

protected
def secure?
     ["list", "add", "show"].include?(action_name)
end

...

the list action will be called (and displayed) from index since the index action is not protected.

Variation: Regex for secure actions

One thing I do is define a class variable regex in my controllers that dictates which methods are available

@@public = /blah|blah/
then my global authentication only requires authentication if action_name =~ @@public

In application.rb:

@@private = // # default to nothing private
protected
def secure?
     action_name =~ @@private
end

Then redifine the regexp per controller:

@@private = /user.*$/ # require login for all user* methods

Comment: i love this idea, but it sucks to add this after the fact because you have to rename, actions, rhtml files, and all the links – i wish there was something that combined both.


Variation: Level-based access

Sometimes you might want to limit actions based on an arbitrarily defined level, so that you can restrict certain classes of user to certain actions. One way of doing this is:

In application.rb:


    # class variable to define the secure levels in:
    @@secure = {}

    private
        def check_auth
            # if the secure? routine returns a value greater than 0, the action requires a login
            if secure? > 0
                if session['id'].nil?
                    # not authorised yet.
                    session["returnto"] = request.request_uri
                    redirect_to :controller => "users" # or wherever your login page is
                    return false
                else
                    @user = User.find(session['id'])
                    # check the user's level against the level specified for the action
                    if @user.level < secure?
                        render :text => "<p>Action failed due to insufficient user privileges</p>" 
                    end
                end
            end
        end

    protected
        def secure?
            if @@secure.has_key?(action_name)
                @@secure[action_name]
            else
                0
            end
        end

Then, in the controllers that want protecting:


    @@secure = {
        'new' => 10,
        'create' => 10,
        'edit' => 10,
        'update' => 10,
        'destroy' => 10,
    }


See also: LoginGeneratorAccessControlList

Comment: This page was great, and well written, thank you so much.