nov / fb_graph

This gem doesn't support FB Graph API v2.0+. Please use fb_graph2 gem instead.
MIT License
1.04k stars 191 forks source link

Rack::OAuth2::Client::Error (Rack::OAuth2::Client::Error): #251

Closed medinarodel closed 12 years ago

medinarodel commented 12 years ago

Hi

I am getting this error

Rack::OAuth2::Client::Error (Rack::OAuth2::Client::Error):

when my redirect_uri has additional parameters like this:

def facebook_oauth_link(todo) fb_auth = FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret]) client = fb_auth.client client.redirect_uri = enter_url(todo: todo) client.authorization_uri(:scope => [:email, :offline_access, :publish_stream, :user_location, :user_interests]) end

But if this is my code

def facebook_oauth_link fb_auth = FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret]) client = fb_auth.client client.redirect_uri = enter_url client.authorization_uri(:scope => [:email, :offline_access, :publish_stream, :user_location, :user_interests]) end

The purpose of my params(todo) is for me to determine where to redirect the user after the callback.

nov commented 12 years ago

I guess you are missing to pass the same params when exchanging the given authorization code with an access token. At token endpoint (= when exchanging code with token), you need to put exact the same URL which you put in authorization URL. Below is a sample code to enable it.

def client
  FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret], :redirect_uri => enter_url(todo: todo))
end

def authz_url
  client.authorization_uri(:scope => [:email, :offline_access, :publish_stream, :user_location, :user_interests])
end

def exchange_code(code)
  client.authorization_code = code
  client.access_token! :basic_auth
end
medinarodel commented 12 years ago

Wow, thanks for the reply. I understand that I need to put exact the same URL which I put on the authorization URL.

Is there a difference between this:

def client
  fb_auth = FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret])
  client = fb_auth.client
  client.redirect_uri = enter_url(todo: todo)
end

and this:

def client
    FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret], :redirect_uri => enter_url(todo: todo))
end

And also, should I use :basic_auth instead of the one your provided on your docs :client_auth_body

nov commented 12 years ago

Both are the same, if you're touching FbGraph::Auth#client (= Rack::OAuth2::Client) directly.

And sorry, please use :client_auth_body or anything other than :basic. It's my mistake. FB doesn't support putting client credentials in basic authentication header.

medinarodel commented 12 years ago
def client(todo)
  FbGraph::Auth.new(FACEBOOK[:id], FACEBOOK[:secret], :redirect_uri => enter_url(todo: todo))
end
def authz_url(todo)
  client(todo).authorization_uri(:scope => [:email, :offline_access, :publish_stream, :user_location, :user_interests])
end

Let us say enter_url = "/enter"

Based from the code above, I am redirecting my Login via Facebook using authz_url(users_path). Facebook will then redirect it back to my callback URL which should be: "/enter?todo=[encoded users_path]&code=[FROM_FACEBOOK]"

Here is the code for my callback which is still causing the error:

@client = client(params[:todo]) #I also tried URI.decode(params[:todo])
@client.authorization_code = code
@client.access_token! :client_auth_body
nov commented 12 years ago

I assume params[:todo] is nil when you get the authorization code via redirect, since it's not the same HTTP request anymore. To do so, you'll need to store the params[:todo] value in session or cookie before redirecting to FB's authorization endpoint.

BTW, OAuth 2.0 defines state parameter to identify additional session info, and FB also support it. https://developers.facebook.com/docs/reference/dialogs/oauth/

Using state parameter, the code could be

class FBOAuthController
  def start
    state = SecureRandom.hex(8)
    session[state] = params[:todo]
    redirect_to client.authorization_uri(:state => state, :scope => YOU_DEFINE)
  end

  def callback
    client.authorization_code = params[:code]
    todo = session[params[:state]]
    token = client.access_token! :client_auth_body
    # TODO: More work here 
  end

  private

  def client
    @client ||= FbGraph::Auth.new(
      FACEBOOK[:id],
      FACEBOOK[:secret],
      :redirect_uri => callback_url # no query params needed here.
    ).client
  end
end

Of course, you can put params[:todo] value itself in the redirect_uri query as you are doing now though.

ps. Main purpose of state is avoiding CSRF attack on your callback endpoint. So if you use state correctly, you'll get a security advantage too. ref.) http://tools.ietf.org/html/draft-ietf-oauth-v2-30#section-10.12

nov commented 12 years ago

BTW, the most simplest & secure way would be using FB JS SDK & XFBML Login Button.

ref.) https://developers.facebook.com/docs/reference/plugins/login/ http://facebook.stackoverflow.com/questions/5200167/how-to-let-facebook-login-button-redirect-to-a-particular-url https://github.com/nov/fb_graph/wiki/Authentication

medinarodel commented 12 years ago

the params[:todo] is being passed together with the params[:code] and it is not nil BTW, my original implementation was Javascript SDK but I encountered reoccurring issue for cookie not found so I changed it to OAUTH which I found more stable but I just encountered this reported problem if I added another params on redirect

nov commented 12 years ago

Ah, you are right. I was confusing.

Then we probably need to check the exact redirect_uri value. Can you put this file in your rails project? Then you'll see raw HTTP request & response in your rails log. https://github.com/nov/fb_graph_sample/blob/master/config/initializers/debugger.rb

ps. I modified my fb_graph_sample app to use query parameter in redirect_uri as below, and it's working fine. https://github.com/nov/fb_graph_sample/

class FacebooksController < ApplicationController
  :

  # handle Normal OAuth flow: start
  def new
    client = Facebook.auth(callback_facebook_url(hoge: "http://example.com")).client
    redirect_to client.authorization_uri(
      :scope => Facebook.config[:scope]
    )
  end

  # handle Normal OAuth flow: callback
  def create
    client = Facebook.auth(callback_facebook_url(hoge: "http://example.com")).client
    client.authorization_code = params[:code]
    access_token = client.access_token! :client_auth_body
    user = FbGraph::User.me(access_token).fetch
    authenticate Facebook.identify(user)
    redirect_to dashboard_url
  end

  :
end
nov commented 12 years ago

Is this issue still open?

medinarodel commented 12 years ago

Hi! Sorry I was not able to verify this since I changed my implementation.

nov commented 12 years ago

ok, then I close this issue for now.

sidbatra commented 11 years ago

Thanks for all your amazing work on fb_graph. Sorry to open this issue again, but I'm using fb_graph on a production server with 100s of signups a day and a clear (and seemingly) random 5% of our users are getting this error.

I save the redirect_uri in the session, so it's always exactly the same. I'll really appreciate your help with this.

Gem versions: fb_graph v2.5.2 rack v1.1.0

Backtrace: "Rack::OAuth2::Client::Error"

/usr/local/lib/ruby/gems/1.8/gems/rack-oauth2-0.14.4/lib/rack/oauth2/client.rb:110:in handle_error_response' /usr/local/lib/ruby/gems/1.8/gems/rack-oauth2-0.14.4/lib/rack/oauth2/client.rb:87:inhandle_response' /usr/local/lib/ruby/gems/1.8/gems/rack-oauth2-0.14.4/lib/rack/oauth2/client.rb:61:in access_token!' [RAILS_ROOT]/app/controllers/facebook_controller.rb:43:increate' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:1333:in send' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:1333:inperform_action_without_filters' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/filters.rb:617:in call_filters' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/filters.rb:610:inperform_action_without_benchmark' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/benchmarking.rb:68:in perform_action_without_rescue' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.14/lib/active_support/core_ext/benchmark.rb:17:inms' /usr/local/lib/ruby/1.8/benchmark.rb:308:in realtime' /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.14/lib/active_support/core_ext/benchmark.rb:17:inms' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/benchmarking.rb:68:in perform_action_without_rescue' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/rescue.rb:160:inperform_action_without_flash' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/flash.rb:151:in perform_action' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:532:insend' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:532:in process_without_filters' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/filters.rb:606:inprocess' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:391:in process' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/base.rb:386:incall' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/routing/route_set.rb:438:in call' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/dispatcher.rb:87:indispatch' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/dispatcher.rb:121:in _call' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/dispatcher.rb:130:inbuild_middleware_stack' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/strategy.rb:177:in call' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/strategy.rb:177:incall!' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/strategy.rb:157:in call' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/strategy.rb:177:incall!' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/strategy.rb:157:in call' /usr/local/lib/ruby/gems/1.8/gems/omniauth-1.1.1/lib/omniauth/builder.rb:48:incall' /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.3.14/lib/action_controller/string_coercion.rb:25:in call' /usr/local/lib/ruby/gems/1.8/gems/rack-1.1.0/lib/rack/head.rb:9:incall' /usr/local/lib/ruby/gems/1.8/gems/rack-1.1.0/lib/rack/methodoverride.rb:24:in `call'

class FacebookController < ApplicationController
  before_filter :create_client

  # Start the facebook authentication process.
  #
  def new
    @target_url = root_path(:src => HomeShowSource::LoginError)

    session[:fb_redirect_uri] = fb_reply_url(
                                  :src    => @source,
                                  :target => params[:target],
                                  :follow_user_id => params[:follow_user_id],
                                  :usage  => params[:usage])

    @client.redirect_uri = session[:fb_redirect_uri]

    @target_url = @client.authorization_uri(
                  :scope => [:email,
                             :user_likes,
                             :user_birthday,
                             :publish_actions])

    redirect_to @target_url
  end

  # Handle reply from facebook oauth.
  #
  def create
    @client.redirect_uri = session[:fb_redirect_uri]
    @client.authorization_code = params[:code]

    access_token = @client.access_token!(:client_auth_body)
  end

  private

  # Create facebook client variable.
  #
  def create_client
    fb_auth = FbGraph::Auth.new(
                            CONFIG[:fb_app_id],
                            CONFIG[:fb_app_secret])

    @client = fb_auth.client
  end
end