Saturday 10 December 2011

Rails: Facebook and Twitter connect

Summary of the complete facebook and twitter connect flow in a typical rails 3 application, using fbgraph, twitter_oauth.

Gemfile:

gem 'fbgraph', '1.9.0'
gem 'twitter_oauth', '0.4.3'

config/routes.rb:

  match 'login/:platform'  => 'welcome#login',  :as => :login
  match 'auth/:platform/callback' => 'welcome#auth_callback', :as => :auth_callback

config/social_api.yml:

facebook:
  app_key: fbappkey
  app_secret: fbappsecret
  callback_url: http://yourapp.com/auth/facebook/callback

twitter:
  app_key: twtappkey
  app_secret: twtappsecret
  callback_url: http://yourapp.com/auth/twitter/callback

lib/auth_client.rb

# Interacts with FB/Twitter oauth apis
# using fbgraph and twitter_oauth gems resp.
# Designed to be included to any controller.
module AuthClient
  SOCIAL_API_CREDS_MAP = YAML::load_file File.join(Rails.root, 'config/social_api.yml')

  #FB methods
  def fb_client(reload = false)
    opts = {
      :client_id => fb_config['app_key'],
      :secret_id => fb_config['app_secret'],
      :token => session[:fb_access_token]
    }

    @fb_client = FBGraph::Client.new(opts) if reload
    @fb_client ||= FBGraph::Client.new(opts)
  end

  def fb_authorize
    fb_client.authorization.authorize_url(
      :redirect_uri => fb_config['callback_url'],
      :scope => 'email,publish_stream'
    )
  end

  def fb_get_token(code)
    fb_client.authorization.process_callback(code,
      :redirect_uri => fb_config['callback_url']
    )
  end

  # TODO:: we may need to check the access token validity as well.
  def fb_authorized?
    !session[:fb_access_token].blank?
  end

  def fb_config
    SOCIAL_API_CREDS_MAP['facebook']
  end

  # Twitter methods
  def twt_client
    opts = {
      :consumer_key    => twt_config['app_key'],
      :consumer_secret => twt_config['app_secret'],
      :token => session[:twt_access_token],
      :secret => session[:twt_secret_token]
    }
    @twt_client ||= TwitterOAuth::Client.new(opts)
  end

  def twt_get_token
    twt_client.request_token(
      :oauth_callback => twt_config['callback_url']
    )
  end

  def twt_authorize(verifier)
    twt_client.authorize(
      session[:twt_request_token],
      session[:twt_request_token_secret],
      :oauth_verifier => verifier
    )
  end

  def twt_authorized?
    twt_client.authorized?
  end

  def twt_config
    SOCIAL_API_CREDS_MAP['twitter']
  end
end

app/controller/welcome_controller.rb

require 'auth_client'

class WelcomeController < ApplicationController
  include AuthClient

  def login
    self.send(params[:platform] + "_login")
  end

  def auth_callback
    self.send(params[:platform] + "_callback")
  end


private
  def facebook_login
    if fb_authorized?
      usr_obj = fb_client.selection.me.info!
      check_and_handle_fb_user(usr_obj.data)
    else
      auth_url = fb_authorize
      redirect_to auth_url
    end
  end

  def twitter_login
    if twt_authorized?
      usr_hsh = twt_client.info
      check_and_handle_twt_user(usr_hsh)
    else
      resp = twt_get_token
      session[:twt_request_token] = resp.token
      session[:twt_request_token_secret] = resp.secret
      redirect_to resp.authorize_url
    end
  end

  def twitter_callback
    if params[:denied]
      redirect_to login_url(:default), :alert => 'Unauthorized!'
      return
    end

    # Exchange the request token for an access token.
    resp = twt_authorize params[:oauth_verifier]

    if twt_authorized?
      # Storing the access tokens so we don't have to go back to Twitter again
      # in this session.  We can also consider persisting these details in DB.
      session[:twt_access_token] = resp.token
      session[:twt_secret_token] = resp.secret
      usr_hsh = JSON.parse resp.response.body
      check_and_handle_twt_user(usr_hsh)
    else
      redirect_to login_url(:default), :alert => 'Twitter Auth failed!'
    end
  end

  def facebook_callback
    if params[:error] == "access_denied"
      redirect_to login_url(:default), :alert => 'Unauthorized!'
      return
    end

    token = fb_get_token(params[:code])

    if token
      session[:fb_access_token] = token
      usr_obj = fb_client(true).selection.me.info!
      check_and_handle_fb_user(usr_obj.data)
    else
      redirect_to login_url(:default), :alert => 'Facebook Auth failed!'
    end
  end

  def check_and_handle_twt_user(usr_hsh)
    usr = User.find_by_extuid(usr_hsh['id_str'])
    if usr.nil? # new user
      @user = User.new(
        :username => usr_hsh['screen_name'],
        :email => usr_hsh['email'],
        :full_name => usr_hsh['name'],
        :extuid => usr_hsh['id_str'],
        :description => usr_hsh['description'],
        :website => usr_hsh['url']
      )
      @avatar_url = usr_hsh['profile_image_url']
      render :signup
    else
      session[:user_id] = usr.id
      redirect_to home_url
    end
  end

  def check_and_handle_fb_user(usr_obj)
    usr = User.find_by_extuid(usr_obj.id)
    if usr.nil? # new user
      @user = User.new(
        :username => usr_obj.username,
        :email => usr_obj.email,
        :full_name => usr_obj.name,
        :extuid => usr_obj.id,
        :description => usr_obj.bio,
        :website => usr_obj.link
      )
      @avatar_url = fb_client.selection.me.picture
      render :signup
    else
      session[:user_id] = usr.id
      redirect_to home_url
    end
  end
end

app/models/user.rb:

User model with username, email, full_name, extuid, description, website, avatar_url columns.

I'll leave it upto you to handle the views, as you would prefer.

link_to 'Facebook Connect', login_path(:facebook)
link_to 'Twitter Connect', login_path(:twitter)

will help you get started with the action. Hope it helps!

No comments:

Post a Comment