开发者

Switching between web and touch interfaces on Facebook login using Omniauth and Rails 3

The situation:

Using Rails 3 and OmniAuth, I have an app that authenticates using the Facebook strategy. This app has been built to work equally-well for web and开发者_开发知识库 mobile interfaces (ala Jquery-Mobile).

The challenge is to get OmniAuth to provide the mobile version of Facebook's login screen to mobile devices and the web version to desktop devices.

I've hacked together a solution which I'll put as an answer.


Actually, since OmniAuth::Strategies are already Rack middleware, its even simpler. Just override the request_phase method and check the @env instance variable present in the strategy for a mobile user_agent:

module OmniAuth
  module Strategies
    class Facebook < OAuth2

      MOBILE_USER_AGENTS =  'webos|ipod|iphone|mobile'

      def request_phase
        options[:scope] ||= "email,offline_access"
        options[:display] = mobile_request? ? 'touch' : 'page'
        super
      end

      def mobile_request?
        ua = Rack::Request.new(@env).user_agent.to_s
        ua.downcase =~ Regexp.new(MOBILE_USER_AGENTS)
      end

    end
  end
end


For modern devise / omniauth (>= 1.0) use this in your config/initializers/devise.rb:

FACEBOOK_SETUP_PROC = lambda do |env|
  request = Rack::Request.new(env)
  mobile_device = request.user_agent =~ /Mobile|webOS/i
  request.env['omniauth.strategy'].options[:display] = mobile_device ? "touch" : "page"
end

config.omniauth :facebook, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET,
  :scope => 'email,offline_access', :setup => FACEBOOK_SETUP_PROC,
  :client_options => { :ssl => { :ca_file => Rails.root.join("config/ca-bundle.crt").to_s }}


I tried the first solution but I couldn't get it working. After much searching I found out that Omniauth has an option ":setup => true" which allows for dynamic setting of arguments, such as the :display option necessary for Facebook OAuth.

First turn on the :setup option.

config.omniauth :facebook, APP_CONFIG["fb_app_id"], APP_CONFIG["fb_app_secret"], 
    {:scope => 'email, offline_access', :setup => true}

Then add a second route (setup route):

  devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" } do
    get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru'
    get '/users/auth/:provider/setup' => 'users/omniauth_callbacks#setup'
  end

Add this controller. You might already have it if you followed the devise manual.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def setup
    request.env['omniauth.strategy'].options[:display] = mobile_device? ? "touch" : "page"
    render :text => "Setup complete.", :status => 404
  end
end

Add this method into your ApplicationController:

def mobile_device?
  if session[:mobile_param]
    session[:mobile_param] == "1"
  else
    request.user_agent =~ /Mobile|webOS/        
  end
end

Done!


The approved answer works, except I had to change one line. For the current version of omniauth-facebook, I had to set the display option, like this:

options[:authorize_params] = mobile_request? ? { :display => 'touch' } : { :display => 'page' }

You can use 'popup', 'touch', or 'page' from what I've found.


My solution is pretty involved and requires both modifying the OmniAuth Facebook strategy and adding Rack middleware.

First, I added a class attribute and changed a method in OmniAuth::Strategies::Facebook (I put this at the end of my omniauth.rb config file, but it belongs in the lib directory):

module OmniAuth
  module Strategies
    class Facebook < OAuth2
      cattr_accessor :display # new

      def request_phase
        options[:scope] ||= "email,offline_access"
        options[:display] = OmniAuth::Strategies::Facebook.display || nil # new
        super
      end
    end
  end
end

Second, I added a piece of Rack middleware to determine if the request was from a mobile device and then set the display accordingly:

module Rack
  class FacebookMobileOmniauth
    def initialize(app)
      @app = app
    end

    MOBILE_USER_AGENTS =  'palm|blackberry|nokia|phone|midp|mobi|symbian|chtml|ericsson|minimo|' +
                              'audiovox|motorola|samsung|telit|upg1|windows ce|ucweb|astel|plucker|' +
                              'x320|x240|j2me|sgh|portable|sprint|docomo|kddi|softbank|android|mmp|' +
                              'pdxgw|netfront|xiino|vodafone|portalmmm|sagem|mot-|sie-|ipod|up\\.b|' +
                              'webos|amoi|novarra|cdm|alcatel|pocket|ipad|iphone|mobileexplorer|' +
                              'mobile'

    def call(env)
      request = Request.new(env)
      if request.user_agent.to_s.downcase =~ Regexp.new(MOBILE_USER_AGENTS)
        OmniAuth::Strategies::Facebook.display = 'touch'
      else
        OmniAuth::Strategies::Facebook.display = nil
      end   
      return @app.call(env)
    end
  end
end

And lastly, I added the Rack middleware to my config.ru:

    require ::File.expand_path('../config/environment',  __FILE__)
    use Rack::FacebookMobileOmniauth # new
    run Mystupid::Application
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜