Lasso
Identity herding with OAuth
Lasso makes it damn easy to add SSO to your Rails application. Just load in your configuration, add a couple associations, and you are set to hit the trail running, partner.
Flexibility
Lasso works via decorators and attempts to have as few opinions about your setup as possible.
- Can handle one-to-many associations with owners/tokens
- Can handle multiple tokens from the same provider
- Can handle any provider (OAuth 1 or 2) seamlessly by editing a simple configuration
- Seamlessly handles the 5 permutations of authentication (see below)
- Isn’t hard coded to work with one authentication library
- Works well with STI or multiple token classes/controllers
Lasso creates OAuth tokens via nested attributes on whichever object you deem to be the owner of those keys (e.g, current_user, current_user.account, User.new) which makes it one-to-many and quite flexible.
Cases that Lasso gives you hooks for:
- New token + no user logged in = Registration
- New token + user is logged in = Identity claim
- Existing token + no user logged in = Log in
- Existing token + owner logged in = Refresh secret/refresh keys
- Existing token + someone else logged in = Pass to conflict handler
Gettings started
I haven’t made generators for anything, yet. Feel free to skim this README in addition to checking out the Lasso/Authlogic example application that I’ve built.
Walk-through
Configuration
Add this line to your environment.rb:
config.gem ‘lasso’Schema
You are going to want a model with a schema that at least looks like this, you can call it what you wish:
create_table :access_keys, :force => true do |t| t.string "token_a", "token_b", :limit => 999 t.string "service", "type", :null => false t.string "owner_type" t.integer "owner_id" t.datetime "created_at", "updated_at", :null => false end
Model
Go ahead and add your provider details to the model, like so:
class AccessKey < ActiveRecord::Base oauth do provider '37signals' do key 'YOUR_KEY_HERE' secret 'YOUR_SECRET_HERE' site 'https://launchpad.37signals.com' authorize_path '/authorization/new' access_token_path '/authorization/token' end provider 'LinkedIn' do key 'YOUR_KEY_HERE' secret 'YOUR_SECRET_HERE' site 'https://api.linkedin.com' authorize_path '/uas/oauth/authorize' access_token_path '/uas/oauth/accessToken' request_token_path '/uas/oauth/requestToken' end end end
You’ll want to setup the association to your owner model too:
class User < ActiveRecord::Base has_many :access_keys, :dependent => :destroy, :as => :owner accepts_nested_attributes_for :access_keys end
Controller
You are going to want a controller that is able to handle the requests:
class OauthController < ApplicationController processes_oauth_transactions_for :access_keys, :through => lambda { current_user || User.new }, :callback => lambda { oauth_callback_url }, :conflict => :handle_existing_oauth, :login => :handle_oauth_login def handle_oauth_login(user) # TODO: Log in as the user end def handle_existing_oauth(user) # TODO: Merge accounts or display an error end end
And a controller to show the user their AccessKeys:
class AccessKeysController < ApplicationController def index @access_keys = current_user.access_keys end def show @access_key = current_user.access_keys.find(params[:id]) end def destroy access_key = current_user.access_keys.find(params[:id]) access_key.destroy redirect_to access_keys_path end end
Routes
And maybe some routes:
map.resources :access_keys, :only => [:index, :show, :destroy] map.oauth_authorize '/:service/oauth/start', :controller => 'oauth', :action => 'new' map.oauth_callback '/:service/oauth/callback', :controller => 'oauth', :action => 'create'
Usage
Now OAuth is as simple as adding a link:
<%= link_to 'Integrate your account with your 37signals account', oauth_authorize_path(:service => '37signals') %>
Once authorized you can access the keys like so:
AccessKey.all +----+---------+---------+----------+-------------------+------------+----------+-------------------------+-------------------------+ | id | token_a | token_b | service | type | owner_type | owner_id | created_at | updated_at | +----+---------+---------+----------+-------------------+------------+----------+-------------------------+-------------------------+ | 7 | ... | | Facebook | OAuthTwoAccessKey | User | 8 | 2010-11-12 21:15:08 UTC | 2010-11-12 21:15:08 UTC | | 8 | ... | ... | LinkedIn | OAuthOneAccessKey | User | 8 | 2010-11-12 21:17:39 UTC | 2010-11-12 21:17:39 UTC | +----+---------+---------+----------+-------------------+------------+----------+-------------------------+-------------------------+ AccessKey.first.access.get('/me') "{\"id\":\"5805079\",\"name\":\"James Daniels\",\"first_name\":\"James\",\"last_name\":\"Daniels\",\"link\":\"http:\\/\\/www.facebook.com\\/james.uriah\",\"about\":\"Rails\\/CSS\\/Javascript guru hailing from Portland, Maine.\\n\\nTechStars '09 baby!\",\"hometown\":{\"id\":\"108005632552931\",\"name\":\"Eastport, Maine\"}... AccessKey.last.access.get('/v1/people/~').body "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<person>\n <first-name>James</first-name>\n <last-name>Daniels</last-name>\n <headline>Entrepreneur and Web Professional</headline>\n <site-standard-profile-request>\n <url>http://www.linkedin.com/profile?viewProfile=&key=########&authToken=###...
Note on Patches/Pull Requests
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don’t break it in a
future version unintentionally. - Commit, do not mess with rakefile, version, or history.
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) - Send me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2010 James Daniels. See LICENSE for details.