Facebook SDK access tokens and ajax
Asked Answered
S

2

10

I followed the Facebook SDK for PHP docs and created two files, login.php and fb-callback.php, with all the lookup logic in fb-callback.php. When I do this, everything works fine.

But I want to move the lookup logic to get-posts.php and call it via ajax from fb-callback.php. When I do so, I can't seem to get the access token. I get the error noted below, "Access Token: Bad request".

I have registered both fb-config.php and get-posts.php as Valid OAuth Redirect URIs. So how do I get the proper parameters to get-posts.php?

Here are all the associated files:

login.php

<?php
require_once "config.php";

$redirectURL = 'https://' . $_SERVER[ 'SERVER_NAME' ] . '/r/fb-callback.php';
$permissions = ['email','user_photos','user_posts'];
$loginUrl = $helper->getLoginUrl($redirectURL, $permissions);
?>

<a href='<?php echo $loginUrl; ?>'>
<img src='continue-with-facebook.png'>
</a>
?>

config.php

<?php
if( !session_id() ) {
    session_start();
}

require_once '/home/bitnami/vendor/autoload.php';

$fb = new Facebook\Facebook([
  'app_id' => '---',
  'app_secret' => '---',
  'default_graph_version' => 'v3.1',
  ]);

$helper = $fb->getRedirectLoginHelper();
?>

fb-callback.php

<?php
require_once("config.php");
?>
<html>
<head>
  <script type='text/javascript' src='jquery.js'></script>
  <script>
    var $j = jQuery.noConflict();

    $j(document).ready(function () {

      $j.ajax({
        type: "GET",
        url: "get-posts.php",
        cache: false,
        success: function (html) {
          setTimeout(function () {
            $j('#updateDiv').html(html);
          }, 1000);
        }
      });
    });
  </script>
    </head>
    <body>
        <div id='updateDiv'><img src='spinning.gif' alt='processing...'></div> 
    </body>
</html>

get-posts.php

<?php
require_once("config.php");

// get the posts for this user id
try {
  $accessToken = $helper->getAccessToken();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
  // When Graph returns an error
  echo __LINE__ . ' Access Token: Graph returned an error: ' . $e->getMessage();
  exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
  // When validation fails or other local issues
  echo __LINE__ . ' Access Token: Facebook SDK returned an error: ' . $e->getMessage();
  exit;
}

if (! isset($accessToken)) {
  if ($helper->getError()) {
    header('HTTP/1.0 401 Unauthorized');
    echo "Error: " . $helper->getError() . "\n";
    echo "Error Code: " . $helper->getErrorCode() . "\n";
    echo "Error Reason: " . $helper->getErrorReason() . "\n";
    echo "Error Description: " . $helper->getErrorDescription() . "\n";
  } else {
    header('HTTP/1.0 400 Bad Request');
    echo __LINE__ . ' Access Token: Bad request';
  }
  exit;
}

...
Slit answered 11/10, 2018 at 5:59 Comment(4)
You get redirected to your login callback URL with a code parameter, that then gets exchanged for an access token via API call in the next step. Problem is, that parameter never makes it to get-posts.php, so when you try to get the access token in there, it will in all likelihood fail based on that.Latent
Yes, that is clear. So how do I get that parameter to get-posts.php?Slit
You would have to pass it along with the AJAX request - but you might need others as well, for example the state value. But why are you doing this in an AJAX request to begin with, instead of directly in the callback script?Latent
The reason I am doing it in ajax is because it takes a little time and I would like to display a spinning gif while it calculates.Slit
S
3

Here is the actual code that works with explanations to what had to change below:

fb-callback.php

<?php
require_once("config.php");

try {
  $accessToken = $helper->getAccessToken();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
  // When Graph returns an error
  echo __LINE__ . ' Access Token: Graph returned an error: ' . $e->getMessage();
  exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
  // When validation fails or other local issues
  echo __LINE__ . ' Access Token: Facebook SDK returned an error: ' . $e->getMessage();
  exit;
}

if (! isset($accessToken)) {
  if ($helper->getError()) {
    header('HTTP/1.0 401 Unauthorized');
    echo "Error: " . $helper->getError() . "\n";
    echo "Error Code: " . $helper->getErrorCode() . "\n";
    echo "Error Reason: " . $helper->getErrorReason() . "\n";
    echo "Error Description: " . $helper->getErrorDescription() . "\n";
  } else {
    header('HTTP/1.0 400 Bad Request');
    echo __LINE__ . ' Access Token: Bad request';
  }
  exit;
}

$get = "?accessToken=" . $accessToken;
?>
<html>
<head>
  <script type='text/javascript' src='jquery.js'></script>
  <script>
    var $j = jQuery.noConflict();

    $j(document).ready(function () {

      $j.ajax({
        type: "GET",
        url: "get-posts.php<?php echo $get; ?>",
        cache: false,
        success: function (html) {
          setTimeout(function () {
            $j('#updateDiv').html(html);
          }, 1000);
        }
      });
    });
  </script>
    </head>
    <body>
        <div id='updateDiv'><img src='spinning.gif' alt='processing...'></div> 
    </body>
</html>

get-posts.php

<?php
require_once("config.php");

$fb->setDefaultAccessToken($_GET['accessToken']);

...

So what changed? First, I needed to get the access code in fb-callback.php, not get-posts.php. So I moved the logic to get the access code and check for errors back to fb-callback.php. Then I added some logic to pass the access token collected to the parameter string of the AJAX url. (Look for "$get" in two places, once where I set the value and once where I attach it to the AJAX url.)

In get-posts.php, I removed the code that asks the Facebook API for an access token. Instead, I merely set the access token based on what was passed from fb-config.php via GET in the parameter named "accessToken" with the setDefaultAccesstoken() function from the Facebook PHP SDK.

As noted elsewhere, you can keep passing along the value of the access token via GET, REQUEST, or $_SESSION to page after page, so long as you maintain a session and inform Facebook via the setDefaultAccessToken() function at each subsequent page.

Slit answered 30/10, 2018 at 0:11 Comment(0)
A
2

The answer is in the comments, but there are actually better ways.


Firstly, to answer your question directly, the issue is that the "code" passed to the fb-callback.php has not been passed to the script that calls $helper->getAccessToken()

The docs for that function (at https://developers.facebook.com/docs/php/FacebookRedirectLoginHelper/5.0.0) states

Attempts to obtain an access token from an authorization code. This method will make a request to the Graph API and return a response. If there was an error in that process a FacebookSDKException will be thrown. A FacebookSDKException will also be thrown if the CSRF validation fails.

If no authorization code could be found from the code param in the URL, this method will return null.

There are two other points to note:

  • The "state" (cross site forgery token) is part of the session; you do not need to explicitly pass it in so long as you're on the same domain, however you may need session_start() in both login and get-posts scripts There is a parameter to the getAccessToken function which should be the URL of the ORIGINAL

So very simply

  1. Add session_start() to the login script and the get-posts.php
  2. Modify the fb-callback.php to be something like

.

<?php
require_once("config.php");
$code = $_GET['code'];   // warning: add validation code exists, sanitise etc.
?>
<html>
<head>
  <script type='text/javascript' src='jquery.js'></script>
  <script>
    var $j = jQuery.noConflict();

    $j(document).ready(function () {

      $j.ajax({
        type: "GET",
        url: "get-posts.php?code=<?php echo $code; ?>",
        cache: false,
        success: function (html) {
          setTimeout(function () {
            $j('#updateDiv').html(html);
          }, 1000);
        }
      });
    });
  </script>
    </head>
    <body>
        <div id='updateDiv'><img src='spinning.gif' alt='processing...'></div> 
    </body>
</html>

(Note: I hate inline PHP - this is example only)


However: what you should be doing is passing the Access Token between functions and not the oAuth code. The way you have it above (passing the oAuth code) is a one-off; however you can call get-posts a second/third/fourth time and it'll work if you have the access token first.

1) Do the token exchange in fb-callback.php at the top (it's fast, no need for spinner), grab the token (which FB library stores in a session variable/cookie, or you can do it yourself) and then show the HTML page with spinner and AJAX call.

2) Do the token exchange in fb-callback.php at the top (it's fast, no need for spinner), grab the token (which FB library stores in a session variable/cookie, or you can do it yourself) and then quickly redirect to another page that shows the spinner and does AJAX call.

The critical think to note in both of these is you are passing the Access Token; in get-posts.php check "do I have access token; if not - show login button; if so - call facebook".

Askins answered 28/10, 2018 at 11:57 Comment(5)
I followed your code and now get-posts.php throws the error: ---- Cross-site request forgery validation failed. Required GET param "state" missing. ---- I have tried passing "state" along with "code". I have also tried passing "FBRLH_state". I get the same error each time.Slit
State is stored in the session, and not in a parameter (otherwise it defeats the purpose!). You need to get it from $_SESSION and ensure it's still in $_SESSION when you arrive in get-posts.php. Check you have session_start() in all the scripts before you do any facebook work. I have this working across domains using a custom session handler - so it's possible. HOWEVER this should really a totally mute point - do the code/access token swap in fb-callback.php as it's designed to work, and pass the access token - easier and more flexible.Askins
As noted in my code above, the config.php file included in all the other files has session_start() in it. The only reason I attempted to send "state" and "FBRLH_state" by GET is because the error explicitly said "Required GET param "state" is missing." Whether I send nothing by GET or only "code" as you suggested above, either way when I get to get-posts.php $_SESSION['code'] has a value, $_SESSION['state'] has a value, and $_SESSION['FBRLH_state'] does NOT. With or without sending "code" by GET, it gives the error noted earlier.Slit
Now on to the real question: How, specifically, do I "pass the Access Token" as you suggest?Slit
I figured it out. The actual code changes are noted in my answer.Slit

© 2022 - 2024 — McMap. All rights reserved.