Adapting strategies for 1.0

mbleigh edited this page Oct 3, 2011 · 3 revisions

If you've already written an OmniAuth strategy, chances are you'd like to know what exactly it'll take to get it up and running on 1.0. This page is meant to be a guide just for you, to get your legacy strategy running using OmniAuth 1.0's new features and structure as quickly as possible.

Step One: Remove Your Overloaded Initializer

It is now strongly discouraged for you to override the Strategy initializer. Instead there are some convenience methods that allow you to craft your strategy in a more declarative manner. Let's take a real-world example and see what changes came down the pike when it adapts to 1.0. Here was the existing initializer:

class OmniAuth::Strategies::OAuth

  def initialize(app, name, consumer_key=nil, consumer_secret=nil, consumer_options={}, options={}, &block)
    self.consumer_key = consumer_key
    self.consumer_secret = consumer_secret
    self.consumer_options = consumer_options
    self.options[:open_timeout] ||= 30
    self.options[:read_timeout] ||= 30
    self.options[:authorize_params] = options[:authorize_params] || {}

And here's what it looks like after:

class OmniAuth::Strategies::OAuth
  args [:consumer_key, :consumer_secret]

  option :consumer_options, {}
  option :open_timeout, 30
  option :read_timeout, 30
  option :authorize_params, {}

Note that this doesn't do precisely the same thing that the previous code did, but it's very close and works in a much more declarative style. So what actually happened here?

  1. Instead of overriding initialize and calling out to super, we use the args method to declare a list of argument keys that will be optionally passed into the strategy. The corresponding keys in the options object will then be set accordingly. This gives us the same basic functionality as overriding the constructor but lets us do so in a safer, more predictable fashion.
  2. We declare default options with the option class method. This will simply add defaults to the options object on each instantiated strategy that will be overridden by anything the user passes in.
  3. As a result of 1 and 2, everything that is unique configuration for our strategy is now stored in options instead of in instance variables, etc. This makes it maximally easy for the user to customize the strategy dynamically (for instance, altering consumer keys and secrets at runtime).

Name is Optioned

As of OmniAuth 1.0 the required name argument is no more. Instead a :name option can optionally be passed in, and the name defaults to a downcased (not underscored) class name. You can also declare a default name for your strategy with option :name, 'mystrategyname'. Since you should have removed any custom constructor, the refactoring necessary there should already be handled.

Step Two: Convert your auth_hash method into DSL methods

If you wrote a strategy, there's probably an auth_hash method that merges more info into a superclass strategy or just returns info. In OmniAuth 1.0 this functionality has been broken out into DSL methods: info, uid, extra, and credentials. To implement them, just call them with blocks in your class definition:

class OmniAuth::Strategies::MyStrategy < OmniAuth::Strategies::OAuth
  uid { access_token.params['user_id'] }

  info do
      :first_name => raw_info['firstName'],
      :last_name => raw_info['lastName'],
      :email => raw_info['email']

  extra do
    {'raw_info' => raw_info}

  def raw_info

The above is a best-practice example of implementing the various DSL methods (in this case based on an omniauth-oauth strategy). If possible, uid should be determined without making additional calls to an API (this may not always be possible).

Step Three: Packaging

If you're just doing a quick conversion of a strategy, you can still make it public by putting it in omniauth-contrib. All you have to do is submit a pull request including your strategy and it will be rolled into the repository by a maintainer. However, if you're creating a production-ready strategy, you should write robust tests and package it as its own gem. See Packaging OmniAuth Strategies for more details.