CPANに上げたFacebook::OpenGraphの次の実装を進める上で、PHP SDKの実装を参考にしようと思い、中身を見ていて不思議なパラメータを見つけました。5/15のマージでmasterに反映されたもので、HTTPリクエスト送信時、アプリの秘密鍵とアクセストークンを基に生成した文字列をappsecret_proofというクエリパラメータで送るというものです。おまけに「全API呼び出しで必須」というコメントも書かれているのが不気味です。

詳細については「続・PHP SDKに足されたappsecret_proofというパラメータ」という補足エントリを参照してください。

ae2f40afd1cf1f539efbaa7045fba84f


API利用時の流れ

普段はPerlで開発しているため、PHP SDKを扱うのは初めて。まずはリクエストまでの流れを把握するところから始めました。PHPを書くことが無いので、怪しいところがあるかもしれません。
/* 初期化 */
$facebook = new Facebook(array( 'appId' => 'YOUR_APP_ID', 'secret' => 'YOUR_APP_SECRET', ));


/* go.hagiwara オブジェクトを取得 */
$facebook->api('/go.hagiwara', 'GET');

api()は、まず旧REST APIを使うか新Graph APIを使うかを引数を元に判別し、よしなにしてくれます。

  /**
   * Make an API call.
   *
   * @return mixed The decoded response
   */
  public function api(/* polymorphic */) {
    $args = func_get_args();
    if (is_array($args[0])) {
      return $this->_restserver($args[0]);
    } else {
      return call_user_func_array(array($this, '_graph'), $args);
    } 
  }

_graph()が呼ばれ、その中で主に以下の内容を扱います。
  1. HTTPリクエストメソッドの指定
  2. ビデオ投稿か否かを判定してリクエスト先のドメインを指定
  3. _oauthRequest()を実行してHTTPリクエストを送る
  4. 返されるJSONをデコード
  5. エラーであれば例外を投げる
  /**   
   * Invoke the Graph API.
   *
   * @param string $path The path (required)
   * @param string $method The http method (default 'GET')
   * @param array $params The query/post data
   *
   * @return mixed The decoded response object
   * @throws FacebookApiException
   */
  protected function _graph($path, $method = 'GET', $params = array()) {
    if (is_array($method) && empty($params)) {
      $params = $method;
      $method = 'GET';
    } 
    $params['method'] = $method; // method override as we always do a POST
    
    if ($this->isVideoPost($path, $method)) {
      $domainKey = 'graph_video';
    } else {
      $domainKey = 'graph';
    }

    $result = json_decode($this->_oauthRequest(
      $this->getUrl($domainKey, $path),
      $params
    ), true);

    // results are returned, errors are thrown
    if (is_array($result) && isset($result['error'])) {
      $this->throwAPIException($result);
      // @codeCoverageIgnoreStart
    }
    // @codeCoverageIgnoreEnd

    return $result;
  }

送るのは全てPOSTリクエスト

ここで一個注目したのは、リクエスト送信時には全てHTTPリクエストメソッドはPOSTに指定される(method override as we always do a POST)ということです。その代わり、GET, DELETE, POSTなど開発者が指定したリクエストメソッドはmethodというパラメータに指定されて残ります。最終的には以下のようになります。
curl -X POST \
       -F 'method=DELETE' \
       -F 'access_token=XXXXXX' \
       https://graph.facebook.com/OBJECT_ID_TO_DELETE

POSTメソッドとmethodパラメータの併用については、以下の通りGraph APIドキュメントの日本語訳でさらっと紹介されています。
オブジェクトのURLに対してHTTP DELETEリクエストを送信すると、グラフ上のオブジェクトを削除できます。
DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
全HTTPメソッドをサポートしていないクライアント(JavaScriptクライアントなど)をサポートするには、代わりに、オブジェクトのURLに対してmethod=deleteというパラメータを追加してPOSTリクエストを送ります。
この説明だと、一部リクエストメソッドが利用できない場合の代替手段として、POSTメソッドとmethodパラメータの併用ができるというように読み取れます。そのため、公式のPHP SDKの実装が、全てのリクエストに関してこの手段を用いているのが意外でした。
FQLのマルチクエリはGETリクエストで実装するようドキュメントに書かれているため、クエリストリングが長くなりがちです。その長さの上限について調べようとしていたのが今回PHP SDKのコードを読むきっかけだったため、全てPOSTメソッドを利用してパラメータをContent-Bodyに詰め込んでいると分かったところで、当初の目的は達成できました。
が、興味本位でリクエストを投げる部分の実装まで見ていて見つけたのが、appsecret_proofパラメータです。

appsecret_proofパラメータ


  protected function _oauthRequest($url, $params) {
    if (!isset($params['access_token'])) {
      $params['access_token'] = $this->getAccessToken();
    }

    if (isset($params['access_token'])) {
      $params['appsecret_proof'] = $this->getAppSecretProof($params['access_token']);
    }

    // json_encode all params values that are not strings
    foreach ($params as $key => $value) {
      if (!is_string($value)) {
        $params[$key] = json_encode($value);
      }
    }

    return $this->makeRequest($url, $params);
  }
この中でgetAppSecretProof()にアクセストークンを渡し、appsecret_proofに指定する文字列を生成しています。
  /**
   * Generate a proof of App Secret
   * This is required for all API calls originating from a server
   * It is a sha256 hash of the access_token made using the app secret
   *
   * @param string $access_token The access_token to be hashed (required)
   *
   * @return string The sha256 hash of the access_token
   */
  protected function getAppSecretProof($access_token) {
    return hash_hmac('sha256', $access_token, $this->getAppSecret());
  }
ここで怖いのは、以下のコメントです。
This is required for all API calls originating from a server
It is a sha256 hash of the access_token made using the app secret
これはサーバからの全API呼び出しで必須となるものです。
アプリの秘密鍵とアクセストークンから生成するSHA256ハッシュ値です。
ただし、新しくアプリを作成し、テストユーザを生成して試してみましたが、適当な文字列を入れてみても何も問題は起きませんでした。appsecret_proofが渡された場合のバリデーションなどはまだ実装されていないのでしょうか。

>curl -X POST \
> -F 'access_token=テストユーザのアクセストークン' \
> -F 'method=GET' \
> -F 'appsecret_proof=qwrty' \
> https://graph.facebook.com/me

{"id":"100005943794526","name":"Ruth Ameidcgidebf Panditstein","first_name":"Ruth","middle_name":"Ameidcgidebf","last_name":"Panditstein","link":"http:\/\/www.facebook.com\/profile.php?id=100005943794526","gender":"female","timezone":0,"locale":"en_US","updated_time":"2013-05-24T11:25:55+0000"}
ただし、これに絡むエラーが返されているというケースもあるようで、今後が気になります。
Fatal error: Uncaught GraphMethodException: API calls from the server require an appsecret_proof argument thrown in .../.../lib/base_facebook.php on line 1238