Identity herding with OAuth
Ruby
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
spec
.document
.gitignore
LICENSE
README.textile
Rakefile
VERSION
lasso.gemspec

README.textile

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:

  1. New token + no user logged in = Registration
  2. New token + user is logged in = Identity claim
  3. Existing token + no user logged in = Log in
  4. Existing token + owner logged in = Refresh secret/refresh keys
  5. 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=&amp;key=########&amp;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.