Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
305 views
in Technique[技术] by (71.8m points)

javascript - Facebook NoAuthorizationError after passing signed_parameters manually

I've had a lot of issues trying to get the client-side login to work, so I'm going to take the liberty of referencing a ton of other questions here... none have resulted in an answer that has worked for me.

CONTEXT

  • Server-side login works fine
  • Client-side login using JS SDK works fine on Safari* (have not tested in Firefox or IE or mobile non-Chrome), but not in Chrome, which is what this question is about (and majority of my users use Chrome so it's super important)
  • Gem versions:
    • ruby (2.1.2)
    • rails (4.1.1)
    • oauth (0.4.7)
    • oauth2 (1.0.0)
    • omniauth (1.2.2)
    • omniauth-facebook (2.0.0)
    • omniauth-oauth2 (1.2.0)
  • This is for an app in development mode, where as the developer, I am definitely authorized to log in

*By works fine, I mean if you just copied the code from Ryan Bates' RailsCast (http://railscasts.com/episodes/360-facebook-authentication?view=asciicast) it works without any additional anything


TL:DR; I'm passing the following URL which should work...

`http://localhost:3000/auth/facebook/callback?signed_request=ASDF.JOUYOUY`
// Obviously signed_request is a much longer string

...but am still getting the error OmniAuth::Strategies::Facebook::NoAuthorizationCodeError (must pass either acodeparameter or a signed request (viasigned_requestparameter or afbsr_XXXcookie)):


TROUBLESHOOTING TO DATE + OVERVIEW OF MANY OTHER ERRORS/ POTENTIAL SOLUTIONS

The code solutions below build on the code from Ryan Bates' RailsCast:

$('#sign_in').click (e) ->
  e.preventDefault()
  FB.login (response) ->
    window.location = '/auth/facebook/callback' if response.authResponse

Hurdle #1: Are 3rd party cookies blocked?

Doesn't lead to an error necessarily, just makes it so that you can't connect the app to Facebook.

Connecting your app to Facebook first requires you to be logged in with Facebook because the code FB.login (response) -> window.location = "/auth/facebook/callback" if response.authResponse depends on the validity of authResponse and its contained signedRequest parameter.

If cookies are blocked (i.e., Chrome Settings > Advanced Settings > Privacy > Content Settings > Block Third Party Cookies is CHECKED), you will never get a authResponse object back. This is the question/answer combination here: FB.getLoginStatus always returns status='unknown'. In other words, if you do a FB.getLoginStatus, regardless of how many times you click a Login button, the status will always return back as unknown per the docs: https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus.

Code Solution so far...: If FB.login returns a response of unknown divert back to server-side login.

$('#sign_in').click (e) ->
  e.preventDefault()
  FB.login (response) ->
    if response.status is "unknown"
      window.location = "/auth/facebook"
    else
      window.location = "/auth/facebook/callback"

Hurdle #2: Are parameters passed appropriately?

If you have third party cookies unblocked, you might then encounter an error:

OmniAuth::Strategies::Facebook::NoAuthorizationCodeError (must pass either a `code` parameter or a signed request (via `signed_request` parameter or a `fbsr_XXX` cookie)):

Despite setting cookie: true in the FB.init, sometimes params are still not passed. As stated in:

...you might need to manually pass in the signed_request parameter. Quick watch-out. Per Facebook docs (https://developers.facebook.com/docs/facebook-login/using-login-with-games), the signedRequest needs to have two components separated by a .. Not sure how, but I have gotten one without the . before. The separation is important, because it's the latter half that is a base64url coded JSON object containing the code information.

Code Solution so far...: Manually append signed_request parameter to callback URL

$('#sign_in').click (e) ->
  e.preventDefault()
  FB.login (response) ->
    if response.status is "unknown"
      window.location = "/auth/facebook"
    else
      window.location = ("/auth/facebook/callback?signed_request=" + response.authResponse.signedRequest)

Hurdle #3: Still nothing... still same error as #2

Maybe just me at this point?

Code Solution so far...: Go super pedantic, parse out the code from the signedRequest and append to callback URL

$('#sign_in').click (e) ->
  e.preventDefault()
  FB.login (response) ->
    if response.status is "unknown"
      window.location = "/auth/facebook"
    else
      parsed_signed_Request = response.authResponse.signedRequest.split(".")
      key_info = base64_decode(parsed_signed_Request[1])
      // decoded via http://www.simplycalc.com/base64-source.php
      key_info_object = jQuery.parseJSON( key_info )
      window.location = ("/auth/facebook/callback?code=" + key_info_object["code"])

Hurdle #4: CSRF detected, but not for why you'd think

Now I've moved right into a brick wall. When I run the code as above, I get an error CSRF detected. There are a non-related reason one might get this error:

  • You can get this error if your Facebook app is in Dev mode and you are trying to login users live. In this case, FB doesn't allow any non-listed developers to log in. See first answer to this question: Rails + omniauth + facebook - csrf detected

But the problem in my case wasn't the above, it was that the code parameter was presented without a state parameter. Now, there have been answers saying that you can fix this by setting provider_ignores_state: true in the omniauth.rb config file, see second answer to question referenced above, BUT this is not a fix for me. Why? Because 1) I don't like turn things specifically designed to counter CSRF off and 2) it appears the config kicks in only for server-side log-in. Adding it simply didn't do anything for me.

Which means the bigger issue of the #3 solution was that it was trying to combine server-side login approach (takes code and state params) with client-side login approach (takes signed_request param).

Which means I'm back to the original question... how to pass the signed_request so that the client-side login works?


Since I've rambled on this much already, let me point out another error I've seen. This one has answers related to Facebook errors (Dealing with Oauth 2.0-facebook gem error 100: This authorization code has been used), but beyond that, I found something else that could trigger it.

As suggested in this tutorial (https://coderwall.com/p/bsfitw), you match the callback route via both get and post. But when I do this, my logger shows two requests to Facebook, the second obviously being blocked and triggering the error. (Also means that the first request did go through and the user is already authorized/ data is saved, whatever). My solution was to set route as so:

match 'auth/:provider/callback', to: 'signups#create_facebook', via: [:get]
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

For those who still have this problem. Try not to use "localhost:3000" or whatever. Change /etc/hosts to a new url like

127.0.0.1 abc.com

Change facebook app setting at developers.facebook.com to point to the new url (instead of using localhost:3000, call it abc.com:3000)

and try the new login with js facebook login again. Should work fine!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...