Rack-CAS
Rack-CAS is simple Rack middleware to perform CAS client authentication.
Features
- Rack based
- Framework independent Works with, but doesn't depend on Rails, Sinatra, etc.
- Minimal dependencies Current gem dependencies are rack, addressable and nokogiri.
- Supports CAS extra attributes Extra attributes are a mess though. So let me know if your brand of CAS server isn't supported.
- Single sign out One of the included session stores must be used.
- Rake tasks to prune stale sessions
rack_cas:sessions:prune:active_record
andrack_cas:sessions:prune:mongoid
Requirements
- Ruby >= 2.0
- A working CAS server
- An app that returns a
401 Unauthorized
status when authentication is required
Installation
Rails
Add gem 'rack-cas'
to your Gemfile
and run bundle install
Once the necessary gems have been installed, in your config/application.rb
add:
config.rack_cas.server_url = 'https://cas.example.com/'
If the the server URL depends on your environment, you can define it in the according file: config/environments/<env>.rb
Protocol
Since protocol p3
the protocol is prepended in certain urls. If you wish to use protocol p3
set the following config variable
config.rack_cas.protocol = 'p3'
Single Logout
If you wish to enable single logout you'll need to modify your configuration as below.
Active Record
Set the session_store
in your config/application.rb
:
require 'rack-cas/session_store/active_record'
config.rack_cas.session_store = RackCAS::ActiveRecordStore
Edit your config/initializers/session_store.rb
file with the following:
require 'rack-cas/session_store/rails/active_record'
Rails.application.config.session_store ActionDispatch::Session::RackCasActiveRecordStore
Run:
rails generate cas_session_store_migration
rake db:migrate
Mongoid
Set the session_store
in your config/application.rb
:
require 'rack-cas/session_store/mongoid'
config.rack_cas.session_store = RackCAS::MongoidStore
Edit your config/initializers/session_store.rb
file with the following:
require 'rack-cas/session_store/rails/mongoid'
YourApp::Application.config.session_store ActionDispatch::Session::RackCasMongoidStore
Redis
Set the session_store
in your config/application.rb
:
require 'rack-cas/session_store/redis'
config.rack_cas.session_store = RackCAS::RedisStore
Edit your config/initializers/session_store.rb
file with the following:
require 'rack-cas/session_store/rails/redis'
YourApp::Application.config.session_store ActionDispatch::Session::RackCasRedisStore
Optionally, Set the redis_options
in your config/application.rb
.
You can specify anything Redis.new
allows.
For example:
config.rack_cas. = {path: '/tmp/redis.sock',driver: :hiredis}
Sinatra and Other Rack-Compatible Frameworks
Add gem 'rack-cas'
to your Gemfile
and run bundle install
Add the following to your config.ru
file:
require 'rack/cas'
use Rack::CAS, server_url: 'https://login.example.com/cas'
See the example Sinatra app to get started.
Single Sign Out
You will need to store sessions in session store supported by Rack CAS.
Active Record
Add a migration that looks roughly like
class AddSessionStore < ActiveRecord::Migration
def change
create_table :sessions do |t|
t.string :cas_ticket
t.string :session_id
t.text :data
t.datetime :created_at
t.datetime :updated_at
end
end
end
Then use the middleware with
require 'rack-cas/session-store/rack/active_record'
use Rack::Session::RackCASActiveRecordStore
Configuration
Extra Attributes
You can whitelist which extra attributes to keep.
In your config/application.rb
:
config.rack_cas.extra_attributes_filter = %w(some_attribute some_other_attribute)
Excluding Paths
If you have some parts of your app that should not be CAS authenticated (such as an API namespace), just pass exclude_path
to the middleware. You can pass in a string that matches the beginning of the path, a regular expression or an array of strings and regular expressions.
use Rack::CAS, server_url: '...', exclude_path: '/api'
use Rack::CAS, server_url: '...', exclude_path: /\.json/
use Rack::CAS, server_url: '...', exclude_paths: ['/api', /\.json/]
The same options can be passed to FakeCAS
.
use Rack::FakeCAS, exclude_path: '/api'
Excluding Requests
If the path exclusion is not suitable to ignore the CAS authentication in some parts of your app, you can pass
exclude_request_validator
to the middleware with a custom validator. You need to pass a Proc
object that will accept
a Rack::Request
object as a parameter.
use Rack::CAS, server_url: '...', exclude_request_validator: Proc.new { |req| req.env['HTTP_CONTENT_TYPE'] == 'application/json' }
Service URL
Sometimes you need to force the service=
attribute on login requests, and not just use the request url in an automatic way.
use Rack::CAS, service: 'http://anotherexample.com'
Ignore 401 Intercept
For some requests you might want to ignore the 401 intercept made by the middleware. For example when we want CAS to
authenticate API requests but leave the redirect handling to the client. For this you can use the
ignore_intercept_validator
. You need to pass a Proc
object that will accept a Rack::Request
object as a parameter.
use Rack::CAS, server_url: '...', ignore_intercept_validator: Proc.new { |req| req.env['HTTP_CONTENT_TYPE'] == 'application/json' }
use Rack::CAS, server_url: '...', ignore_intercept_validator: Proc.new { |req| req.env['PATH_INFO'] =~ 'api' }
SSL Cert Verification
If you're working in development or staging your CAS server may not have a legit SSL cert. You can turn off SSL Cert verification by adding the following to config/application.rb
.
config.rack_cas.verify_ssl_cert = false
CAS Login Renew Flag
The CAS standard allows for a renew=true
parameter to be passed to the CAS server which will force the user to re-login every time CAS authentication is performed, for added security. To enable this for your application, add the following to config/application.rb
.
config.rack_cas.renew = true
Integration
Your app should return a 401 status whenever a request is made that requires authentication. Rack-CAS will catch these responses and attempt to authenticate via your CAS server.
Once authentication with the CAS server has completed, Rack-CAS will set the following session variables:
request.session['cas']['user'] #=> johndoe
request.session['cas']['extra_attributes'] #=> { 'first_name' => 'John', 'last_name' => ... }
NOTE: extra_attributes
will be an empty hash unless they've been configured on your CAS server.
Testing
Controller Tests
Testing your controllers and such should be as simple as setting the session variables manually in a helper.
def set_current_user(user)
session['cas'] = { 'user' => user.username, 'extra_attributes' => {} }
end
Integration Tests
Integration testing using something like Capybara is a bit trickier because the session can't be manipulated directly. So for integration tests, I recommend using the provided Rack::FakeCAS
middleware instead of Rack::CAS
.
require 'rack/fake_cas'
use Rack::FakeCAS
In addition you can pass a Hash to configure extra attributes for predefined usernames.
use Rack::FakeCAS, {}, {'john' => {'name' => 'John Doe'}}
If you are using Rails, FakeCAS is automatically used in the test environment by default. If you would like to activate it in any other environment, add the following to the corresponding config/environments/<env>.rb
:
config.rack_cas.fake = true
You can also configure extra attribute mappings through the Rails config:
config.rack_cas.fake_attributes = { 'john' => { 'name' => 'John Doe' } }
Then you can simply do the following in your integration tests in order to log in.
visit '/restricted_path'
fill_in 'username', with: 'johndoe'
fill_in 'password', with: 'any password'
'Login'
NOTE: The FakeCAS middleware will authenticate any username with any password and so should never be used in production.