Rack::Casual

A simple Rack middleware that does authentication using CAS or a token. It kicks in whenever a 401 response is returned from the server.

Tested with

  • Ruby 1.8.7 / 1.9.2
  • CAS 2.0 using rubycas-server
  • Rails 3 and ActiveRecord 3
  • Sinatra 1.0

Although ActiveRecord is not required, it uses ActiveRecord-ish methods to find and create users. See examples/sinatra_app.rb for an example of which required methods the user model must support.

Installation

Sinatra

$ gem install 'rack-casual'

See examples/sinatra_app.rb for a sample Sinatra app.

Rails 3

Add this to your Gemfile:

$ gem 'rack-casual'

Run bundle install, and add a configuration file:

$ rails generate rack_casual

This creates a config/initializers/rack-casual.rb file. Make sure cas_url points to your CAS server. If your user model is called something other than "User", you can change this here.

Next you must configure your application to use the plugin. For Rails3, you can add this to your config/application.rb:

config.middleware.use "Rack::Casual::Authentication"

Finally, to authenticate your users, add a before_filter to your controller:

class ApplicationController < ActionController::Base
  before_filter :authenticate!
end

If you want to have a named route to the CAS servers logout url, you can do this:

# config/routes.rb
match '/logout' => redirect(Rack::Casual::Client.logout_url), :as => :logout

If you pass a :url then the CAS server should display a message telling the user to follow the given link.

# config/routes.rb
match '/logout' => redirect(Rack::Casual::Client.logout_url(:url => "http://foo.example.org/logged_out")), :as => :logout

Usage

Rack::Casual adds some helper methods to ActionController::Base

  • logged_in? Returns true if session contains user-id

  • current_user Returns the currently logged in user.

  • authenticate! This is the method you want to use in a before_filter

Authentication token

CAS is nice and all that, but it's not so nice for webservices. Therefore Rack::Casual can authenticate requests using a token. Make sure your User model has a auth_token attribute. You can call it whatever you want, but it defaults to auth_token.

From your client you can now authenticate using a token: http://your-app.com/my-protected-webservice?auth_token=secret

If there are no users with that token, the client just receives the 401 error. It does not fallback to CAS or create a user automatically (doh).

Authorization

Rack::Casual calls active? on your user model if that method exists to determine whether the user can log in or not. So just add this to control whether authenticated users can log in or not.

# app/models/user.rb
class User < ActiveRecord::Base
  def active?
    # i'm sure you can figure something out...
  end
end

Extra attributes

When creating users automatically, Rack::Casual can also add extra attributes if your CAS server provides this. For this to work your User model must have a cas_extra_attributes= instance method. Here's an example:

class User < ActiveRecord::Base
  def cas_extra_attributes=(extra_attributes)
    extra_attributes.each do |name, value|
      case name.to_sym
        when :name   then self.name   = value
        when :email  then self.email  = value
        when :phone  then self.phone  = value
      end
    end
  end
end

Tracking

If you have enabled tracking, Rack::Casual can update the logged in user with information about last login time and IP. These variables will be updated if they are present in your User model:

  • last_login_at (datetime)
  • last_login_ip (string)
  • login_count (integer)

Skipping URLs

I couldn't find an easy way to disable a Rack Middleware in Rails, so I added a configure option to Rack::Casual called ignore_url. Rack::Casual will not be called when the request.path matches the pattern in config.ignore_url.

Useful if you want a basic http authentication for /admin with a predefined set of users that is not part of your CAS infrastructure. Just set config.ignore_url = '^/admin' and Rack::Casual won't do anything when accessing URLs that matches /admin

Known issues

If Rack::Casual fails to create the user you'll end up in a redirect loop.

TODO

More tests dammit.

Copyright (c) 2010 Gudleik Rasch [email protected], released under the MIT license