7

保存された長期アクセス トークンを使用しようとしていますが、2 時間後にグラフ API から次のエラーが発生します。ユーザーを Facebook に送信して、アクセス トークンと交換できる新しいコードを取得するコードをいくつか作成しました。これは、後続のすべてのページ リクエストで発生することを除いて問題なく動作します。Facebook は、以下のエラーでアクセス トークンを無効にし続けます。そのアクセストークンがサーバーから返されたにもかかわらず。

Error validating access token: Session has expired at unix time 1338300000. The current unix time is 1338369365.

完全なテスト ページの例を以下に示します。明らかな理由でキーを省略しています。ページにアクセスしてログインし、数時間放置してから再度ページにアクセスし (Facebook へのリダイレクトを取得し、URL のコードを使用して戻ってきます)、ページをリロードすると、Facebook へのリダイレクトが繰り返されます。 、イベントですが、上記のコードに対して返されたばかりのアクセス トークンを使用するように指示しています。

<?php
  require 'facebook.php';

  $app_id = APP_ID;
  $app_secret = APP_SERCRET;
  $my_url = URL;

  $facebook = new Facebook(array(
    'appId'   => $app_id,
    'secret'  => $app_secret
  ));

  // known valid access token stored in a database
  $access_token = isset($_COOKIE["FB_LONG_AC_TOKEN"]) ? $_COOKIE["FB_LONG_AC_TOKEN"] : false;

  $code = $_REQUEST["code"];

  // If we get a code, it means that we have re-authed the user
  //and can get a valid access_token.
  if (isset($code)) {
    $token_url="https://graph.facebook.com/oauth/access_token?client_id="
      . $app_id . "&redirect_uri=" . urlencode($my_url)
      . "&client_secret=" . $app_secret
      . "&code=" . $code . "&display=popup";
    $response = file_get_contents($token_url);
    $params = null;
    parse_str($response, $params);
    $access_token = $params['access_token'];
  }


  // Attempt to query the graph:
  $graph_url = "https://graph.facebook.com/me?"
    . "access_token=" . $access_token;
  $response = curl_get_file_contents($graph_url);
  $decoded_response = json_decode($response);

  //Check for errors
  if ($decoded_response->error) {
  // check to see if this is an oAuth error:
    if ($decoded_response->error->type== "OAuthException") {
      // Retrieving a valid access token.
      $dialog_url= "https://www.facebook.com/dialog/oauth?"
        . "client_id=" . $app_id
        . "&redirect_uri=" . urlencode($my_url);
      echo("<script> top.location.href='" . $dialog_url
      . "'</script>");
    }
    else {
      echo "other error has happened";
    }
  }
  else {
  // success
    echo("Success: ".$decoded_response->name."<br />");
    echo($access_token."<br />");

    // Attempt to convert access token to longlife token if we don't have one stored.
    if (!isset($_COOKIE["FB_LONG_AC_TOKEN"]))
    { // don't have long life token, so let's get one.
      $ch = curl_init("https://graph.facebook.com/oauth/access_token?client_id=".$app_id."&client_secret=".$app_secret."&grant_type=fb_exchange_token&fb_exchange_token=".$access_token);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
      curl_setopt($ch, CURLOPT_TIMEOUT, 10);
      $data = curl_exec($ch);
      curl_close($ch);

      $params = null;
      parse_str($data, $params);

      if (isset($params["access_token"]))
      {
        $access_token = $params["access_token"];

        echo("Got long life token.<br />");
        setcookie("FB_LONG_AC_TOKEN", $access_token, time() + (3600 * 24 * 60), "/");
      }
    }
    else {
      echo("Have long life token already.<br />");
    }
  }

  if ($access_token) {
    $facebook->setAccessToken($access_token);

    // See if there is a user from a cookie
    $user = $facebook->getUser();

    if ($user) {
      try {
        // Proceed knowing you have a logged in user who's authenticated.
        $user_profile = $facebook->api('/me');
      } catch (FacebookApiException $e) {
        echo '<pre>'.htmlspecialchars(print_r($e, true)).'</pre>';
        $user = null;
      }
    }
  }

  // note this wrapper function exists in order to circumvent PHP’s
  //strict obeying of HTTP error codes.  In this case, Facebook
  //returns error code 400 which PHP obeys and wipes out
  //the response.
  function curl_get_file_contents($URL) {
    $c = curl_init();
    curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($c, CURLOPT_URL, $URL);
    $contents = curl_exec($c);
    $err  = curl_getinfo($c,CURLINFO_HTTP_CODE);
    curl_close($c);
    if ($contents) return $contents;
    else return FALSE;
  }
?>
<!doctype html>
<html xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
    <title>Facebook Auth</title>
</head>
<body>
  <?php if ($user) { ?>
    Your user profile is
    <pre>
      <?php print htmlspecialchars(print_r($user_profile, true)) ?>
    </pre>
  <?php } else { ?>
    <fb:login-button></fb:login-button>
  <?php } ?>

  <div id="fb-root"></div>
  <script>
      window.fbAsyncInit = function () {
        FB.init({
          appId: <?php echo($app_id); ?>,
          cookie: true, // enable cookies to allow the server to access the session
          oauth: true, // enable OAuth 2.0
          xfbml: true  // parse XFBML
        });

        FB.getLoginStatus(function (res) {
          console.log(res.status);
        });
      };

      (function(d){
        var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
        js = d.createElement('script'); js.id = id; js.async = true;
        js.src = "//connect.facebook.net/en_US/all.js";
        d.getElementsByTagName('head')[0].appendChild(js);
      }(document));
  </script>
</body>
</html>

私は何を間違っていますか?またはこれはFacebookの問題ですか?

アップデート:

@cpilko が投稿したフローに従うようにコードを更新しましたが、まだ同じ問題が発生しています。無事にログイン、ログアウトできました。ただし、数時間後にテストページにアクセスすると、翌日、提供されたロングライフアクセストークンでセッション期限切れの例外が発生したとします (FB JS SDK は接続されていると認識していますが、サーバーはそうではありませんでした)、ページを更新し、サーバーと FB JS SDK の両方でログインしているように表示され、Facebook から取得したロング ライフ トークンは、最初に試したもの (Cookie に保存されているもの) と同じです。私が理解できないのは、最初に長寿命トークンを使用できない理由です。以下のコードを更新しました。

<?php
  require 'facebook.php';

  $app_id = "XXXXXXXXXXXXX";
  $app_secret = "XXXXXXXXXXXXXXXXXXXX";
  $my_url = "http://swan.magicseaweed.local/facebook/";

  $facebook = new Facebook(array(
    'appId'   => $app_id,
    'secret'  => $app_secret
  ));

  $valid_user = false;

var_dump($_COOKIE);
echo("<br />");

  if (isset($_COOKIE["FB_LONG_AC_TOKEN"]))
  { // Have long term token, attempt to validate.
    // Attempt to query the graph:
    $graph_url = "https://graph.facebook.com/me?"
      . "access_token=" . $_COOKIE["FB_LONG_AC_TOKEN"];
    $response = curl_get_file_contents($graph_url);
    $decoded_response = json_decode($response);

    // If we don't have an error then it's valid.
    if (!$decoded_response->error) {
      $valid_user = true;
      $access_token = $_COOKIE["FB_LONG_AC_TOKEN"];
      echo("Have long life token.<br />");
    }
    else {
      // Stored token is invalid.
      // Attempt to renew token.

      // Exchange short term token for long term.
      $ch = curl_init("https://graph.facebook.com/oauth/access_token?client_id=".$app_id."&client_secret=".$app_secret."&grant_type=fb_exchange_token&fb_exchange_token=".$facebook->getAccessToken());
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
      curl_setopt($ch, CURLOPT_TIMEOUT, 10);
      $data = curl_exec($ch);
      curl_close($ch);

      $params = null;
      parse_str($data, $params);

      if (isset($params["access_token"]))
      {
        $access_token = $params["access_token"];

        echo("Got long life token.<br />");
        setcookie("FB_LONG_AC_TOKEN", $access_token, time() + (3600 * 24 * 60), "/");
      }
      else
      { // Clear invalid token.
        setcookie("FB_LONG_AC_TOKEN", "false", time() - 3600, "/");
        echo("Long life token invalid.<br />");
      }
    }
  }
  else if ($facebook->getUser())
  { // Have short term access token.
    // Verify short term token is valid still.
    try {
        // Proceed knowing you have a logged in user who's authenticated.
        $user_profile = $facebook->api('/me');
    }
    catch (FacebookApiException $e) { }

    if (is_array($user_profile)) { // Have user.
      $valid_user = true;

      // Exchange short term token for long term.
      $ch = curl_init("https://graph.facebook.com/oauth/access_token?client_id=".$app_id."&client_secret=".$app_secret."&grant_type=fb_exchange_token&fb_exchange_token=".$facebook->getAccessToken());
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
      curl_setopt($ch, CURLOPT_TIMEOUT, 10);
      $data = curl_exec($ch);
      curl_close($ch);

      $params = null;
      parse_str($data, $params);

      if (isset($params["access_token"]))
      {
        $access_token = $params["access_token"];

        echo("Got long life token.<br />");
        setcookie("FB_LONG_AC_TOKEN", $access_token, time() + (3600 * 24 * 60), "/");
      }
    }
  }

  if ($access_token) {
    $facebook->setAccessToken($access_token);

    // See if there is a user from a cookie
    $user = $facebook->getUser();

    if ($user) {
      try {
        // Proceed knowing you have a logged in user who's authenticated.
        $user_profile = $facebook->api('/me');
      } catch (FacebookApiException $e) {
        echo '<pre>'.htmlspecialchars(print_r($e, true)).'</pre>';
        $user = null;
      }
    }
  }

  // note this wrapper function exists in order to circumvent PHP’s
  //strict obeying of HTTP error codes.  In this case, Facebook
  //returns error code 400 which PHP obeys and wipes out
  //the response.
  function curl_get_file_contents($URL) {
    $c = curl_init();
    curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($c, CURLOPT_URL, $URL);
    $contents = curl_exec($c);
    $err  = curl_getinfo($c,CURLINFO_HTTP_CODE);
    curl_close($c);
    if ($contents) return $contents;
    else return FALSE;
  }
?>
<!doctype html>
<html xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
    <title>Facebook Auth</title>
</head>
<body>
  <?php if ($user) { ?>
    Your user profile is
    <pre>
      <?php print htmlspecialchars(print_r($user_profile, true)) ?>
    </pre>
  <?php } else { ?>
    <fb:login-button></fb:login-button>
  <?php } ?>

  <div id="fb-root"></div>
  <script>
      window.fbAsyncInit = function () {
        FB.init({
          appId: <?php echo($app_id); ?>,
          cookie: true, // enable cookies to allow the server to access the session
          oauth: true, // enable OAuth 2.0
          xfbml: true  // parse XFBML
        });

        FB.getLoginStatus(function (res) {
          console.log(res.status);
        });
      };

      (function(d){
        var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
        js = d.createElement('script'); js.id = id; js.async = true;
        js.src = "//connect.facebook.net/en_US/all.js";
        d.getElementsByTagName('head')[0].appendChild(js);
      }(document));
  </script>
</body>
</html>
4

2 に答える 2

4

元のトークンの有効期限が切れたときにループに陥る理由は、Facebook が新しいトークンを発行することをコードが許可していないためです。OAuth エラーが発生すると、OAuth ダイアログが呼び出されますが、これは Cookie に新しいトークンを再入力しません。

もう 1 つの問題は、API 呼び出しを行う前に、長期アクセス トークンを短期アクセス トークンで上書きしていることです。

// known valid access token stored in a database
$access_token = isset($_COOKIE["FB_LONG_AC_TOKEN"]) ? $_COOKIE["FB_LONG_AC_TOKEN"] : false;
 ...
if ($access_token) {
$facebook->setAccessToken($access_token); //Loads the short-term token from a cookie!

それが私のコードであれば、2 つの変数を使用し$access_token_tempます$access_token_long

編集

ページ読み込み時のワークフローは次のようにする必要があります。

+ IF one exists, retrieve the long-term token from `$_COOKIE['FB_LONG_AC_TOKEN']` 
    + If it does exist, test to see if it is valid.
    + If valid, use the renew the token and update the cookie if one is returned. (Only one token will be returned per day)
    + Else mark the long-term token as invalid and clear the cookie.
    + Set a `$vaild_user` flag.
+ ELSE IF a new short-term token is available
    + Read the short-term token cookie with the PHP API and exchange this for a long-term token.
    + Store the long-term token in the cookie
    + Clear the short-term token cookie
    + Set a `$valid_user` flag
+ ELSE: The token does not exist or is invalid
    + Redirect the user to the JS API auth dialog and store the returned short term token in a cookie.
    + Reload the page to process this.
+ ENDIF 
+ IF `$valid_user`: Do stuff.

コードにすべての部分が含まれています。ロジックを機能させるには、ロジックをクリーンアップする必要があります。

編集#2:

サーバーであなたのコードを実行しました。ほとんどの場合は機能しますが、出力しているデバッグ情報はヘッダーを時期尚早に送信setcookie()しており、Cookie を設定する機能を阻害しています。

$out = array();最初の近くで宣言してから、すべてのechoandprintステートメントをに変更して、コードを実行するようにしました これを引き続き表示するには、ドキュメントの内$out[] = "What you were echoing or printing before";に追加します。echo implode("\n", $out);<body>

これを行うと、有効なトークンを Cookie に保存し、それが実際に 60 日間の有効期限を持つ長寿命のトークンであることを検証できました。

于 2012-05-30T11:29:11.870 に答える
0

FB は、より長いトークンのサーバー交換を行うことを思いとどまらせているようです。

Re: デスクトップ アプリ: 「ただし、ユーザーが再度アプリにログインしない限り、有効期間が長いユーザー access_token を取得する方法はありません。」https://developers.facebook.com/roadmap/offline-access-removal/

サーバーのタイムゾーン設定を確認しましたか? 更新を伴うoffline_accessトークンをセットアップしましたが、ユーザーが再クリックして更新する必要がありました(自動ではありません)。

また、グラフ呼び出しを非常に簡単に行うことができます

$facebook->api("me"); // no need to add access token, or decode JSON, etc.
于 2012-06-14T17:21:36.560 に答える