Skip to content

Latest commit

 

History

History
184 lines (135 loc) · 6.59 KB

README.textile

File metadata and controls

184 lines (135 loc) · 6.59 KB

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.