タグ「Facebook」が付けられているもの

facebookはアプリを開発するときにテストユーザを作ることができます。これは非常に便利で、複数のユーザでの操作などもテストできます。

これまで、facebookアプリ(というかライブラリ)を自動でテストするときにテストユーザも自動で作っていました。過去記事を参照。他にも例えば、Qiitaの投稿に手順が載っています。

Facebookアプリのテストユーザーの作り方手順

ところが、このlogin_urlが使えなくなっているのに気づきました。404エラーになってしまうのです。Google検索すると、

http://stackoverflow.com/questions/22583963/404-error-trying-to-use-facebook-test-user-login-url

がヒットしました。今回は、stackoverflowでいい投稿を見つけました。すばらしい。stackoverflowは便利なときは便利です。facebookにバグ報告していて、

https://developers.facebook.com/bugs/276245435872240/

これによると、ポリシーが変わって使えなくなったと。「ドキュメントもアップデートしてくれ」と要望がでてますね。さて、stackoverflowのAnswerにはworkaroundも出ていて、簡単に回避できました。この方法が止められなければいいのですが。

参考までに、node.jsでのテストユーザのログインのコードの抜粋を載せておきます。

request.get('https://www.facebook.com', function() {
  request.post('https://www.facebook.com/login.php', {
    followAllRedirects: true,
    form: {
      email: facebook_user_email,
      pass: facebook_user_password
    }
  }, function(error, response) {
    //check response
  });
});

完全なコードはこちらです。

FacebookのGraph APIで写真を投稿する場合には、/ALBUM_ID/photosのエンドポイントにHTTP POSTするのですが、そのときにmultipart/form-data形式にする必要があります。

https://developers.facebook.com/docs/reference/api/publishing/

詳しくは上記公式ドキュメントを参照してください。

ところで、以前紹介したfacebook-node-sdkでは、form-dataがサポートされていません。

https://github.com/Thuzi/facebook-node-sdk/issues/28

このようにIssueにもなっていましたが、現時点ではサポートされていないようです。PRしてくれとか言っているので、ちょっとのぞいてみましたがrequestが奥の方に埋まっているので、ちょっと手間がかかりそうでした。

とりあえず、動くものを作ってみようと思ったので、上記ライブラリは使わずに普通にrequestだけでやってみたところ、簡単にできてしまいました。

コードを貼り付けておきます。

var request = require('request');

function postMyPhoto(accessToken, message, file, callback) {
  var form = request.post('https://graph.facebook.com/me/photos', function(err, res, data) {
    if (err) return callback(err);
    if (!data || data.error) return callback(data ? data.error : 'unknown error');
    callback(null, data);
  }).form();
  form.append('access_token', accessToken);
  form.append('message', message);
  form.append('source', fs.createReadStream(file));
}

実は、streamを初めて使いました。使ったとは言わないか。 しかし、フローが分かりにくいです。明示的にstreamの終了はないものなのでしょうか。 興味はあるのでまたの機会に勉強しようと思います。

久しぶりの記事です。

social-cms-backendのテストコードをMochaで書いていますが、Facebookのログイン周りのテストをするのに、Facebookのテストユーザを使いました。

Facebookのテストユーザについては、

http://developers.facebook.com/docs/test_users/

にドキュメントがあります。

手順は書いてある通りで、(1)アプリのアクセストークンを取得、(2)テストユーザを作成、(3)専用ログインURLを開いてログインする、だけです。

requestを使ってこれらのステップを実行していたのですが、たったこれだけのことをやるのに何時間もはまりました。

1と2は何の問題もなかったのですが、3がうまく行かず、Facebookのログインページにリダイレクトされてしまうという問題でした。

解決方法は、User-Agentを設定する、です。

ドキュメントには書かれていません。もし同様の問題を抱えている人に参考になればと思います。参考になるか分かりませんが、その時点でのテストコードは、

https://github.com/dai-shi/social-cms-backend/blob/c678043a2ad0ce45bc51b576d77e53c7eac658e5/test/80_facebook_login_test.js

です。

Node.jsで困るのはある機能を満たすためにどのパッケージを使っていいか分からないことです。発展途上ということで納得しましょう。発展途上というか生態系。

今回、Facebook認証をするためのモジュールを探しました。 stackoverflowで色々比較コメントがあり、それらやREADMEを参考にしました。

connect-authというのは名前もいいし、それなりのStar数もあります。シンプルでよさそうなのですが、よく使い方が分かりません。人気があるのは後発のeveryauthのようです。しかし、ドキュメントを読んでみてもどうもピンときません。なにか、感性が合わないような気がしました。

そこで、Passportを試すことにしました。 http://passportjs.org/guide/facebook/にドキュメントがあります。everyauthと比較するとコーディング量は多いかもしれませんが、なんとなくこちらの方が合う気がします。それでも、accessTokenが欲しいだけの場合は、userオブジェクトなんて作らなくてもいいのですけど。これはeveryauthも同じ(?)なので我慢するとします。

簡単に今使ってみた方法を紹介します。

まずは、ライブラリのロードです。サンプルをコピペしただけです。

var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;

続いて、FacebookStrategyの設定です。accessTokenだけ欲しかったので、それをuserオブジェクトにします。必要であれば、profileとかも入れればよいでしょう。userオブジェクトは、req.userで参照できるので、accessTokenはreq.user.accessTokenになります。

passport.use(new FacebookStrategy({
  clientID: process.env.FACEBOOK_APP_ID,
  clientSecret: process.env.FACEBOOK_SECRET,
  callbackURL: process.env.CALLBACK_URL
}, function(accessToken, refreshToken, profile, done) {
  done(null, {
    accessToken: accessToken
  });
}));

サンプルにあったシリアライザも入れておきます。これは書かなくてもよさそうと思ったのですが、ソースみてもデフォルトがあるように見えず、念のためいれておきます。

passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

強制的にログインさせるmiddlewareです。一度、ログインを促すページを表示するほうが親切かもしれません。

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    next();
  } else if (req.url.lastIndexOf('/auth/', 0) >= 0) {
    next();
  } else {
    res.redirect('/auth/facebook');
  }
}

expressの設定です。セッションを使うのでその設定が必要です。

var app = express();
app.use(express.cookieParser());
app.use(express.session({ secret: 'foobar' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(ensureAuthenticated);

最後に、expressのルートの設定をします。failureRedirectは暫定です。

app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
  successRedirect: '/',
  failureRedirect: '/auth/loginfailed'
}));
app.get('/auth/loginfailed', function(req, res) {
  res.send('login failed');
});

以上、こんな感じで使うようです。/auth/facebookのようなルートを設定しなければならないのを面倒と考えるか、分かりやすいと考えるかが、Passportを受け入れられるかの境目かもしれません。

facebook-node-sdkを使って、簡単なFacebook Graph APIの呼び出しをしていたのですが、不思議なエラーがでてました。

(#5) Unauthorized source IP address

というエラーです。

stackoverflowでも複数のスレッドで話題になっていました。IPが変わってしまっていて、エラーになっているということは分かりました。

herokuを使うと、デプロイしてから何時間か経つとIPアドレスが変わるようです。そこで、access tokenを同じものを使い続けると上記エラーがでるので、access tokenを取得し直す必要があります。

これは、OAuthExceptionのときに再取得するようにすればいいのですが、少しはまりました。access tokenを取得するAPIを呼ぶ時に事前にaccess tokenをクリアしておく必要がありました。そうしないと、access tokenを再取得しようとするときに上記のエラーが出ました。Facebook側の問題か、ライブラリ側の問題か難しいところですが、とりあえずは自分で回避するしかないです。

参考までにサンプルコードを載せておきます。

FB.api('hoge/feed', function(res) {
  if (res && res.error && res.error.type === 'OAuthException') {
    FB.setAccessToken(null);
    FB.api('oauth/access_token', {
      client_id: process.env.FACEBOOK_APP_ID,
      client_secret: process.env.FACEBOOK_SECRET,
      grant_type: 'client_credentials'
    }, function(res) {
      if (!res || res.error) {
        console.log('error occurred when getting access token:', res && res.error);
        cb(res);
        return;
      }
      FB.setAccessToken(res.access_token);
      FB.api('hoge/feed', cb);
    });
  } else {
    cb(res);
  }
});

今回はapplication access tokenを使う時に起こったエラーですが、普通のuser access tokenでも同じかもしれません(未確認)。


5/2追記。

上記のコードでも回避できずにエラーになったことが一度だけありました。再度調査中ですがその後再現しないので解決できないです。

5/6追記。

その後安定して動いています。前回の一度起こったエラーはたまたま別の要因があったと思っておきましょう。

久しぶりにFacebookアプリを作ることにしました。以前作った時から仕様がちょっと変わったので、勉強し直しです。

ちなみに、以前作ったアプリは修正していないので、動かなくなってしまっています。Java & JSPでGAE向けに作ったのであまりやる気が出なくなってしまいました。Node.jsで書き直そうかしらと思いつつも、作り直すならもうちょっと全体設計から見直したいと思って手がつけられていません。

話を戻すと、FacebookのGraph APIを使うためのライブラリを調べました。

http://developers.facebook.com/tools/third-party-sdks/#nodejs

にいくつかリストアップされています。

リポジトリの名前が同じものがあり少しややこしいです。NPMのパッケージ名は違います。Star数が同じなのはたまたまのようです。

さて、少しずつ特徴があります。amachang/facebook-node-sdkとnode-facebook-sdkはオリジナルのFacebook PHP SDKを再現したライブラリになっています。Thuzi/facebook-node-sdkはブラウザ向けのFacebook JavaScript SDKと同じAPIをサーバ側でも実現したものです。これはとてもnode.jsらしいと思いました。fbgraphは、また路線が違ってJavaScriptっぽくJSON APIに書き直したような感じです。

今回はFacebookアプリと言ってもあまり複雑なことをするつもりがなかったのと、以前PHP SDKを見たことがある(そして、わざわざJavaに移植した)ので、PHP SDK互換のものを使おうと思いました。二者択一ならとりあえずStar数を信じてみようということで、amachang/facebook-node-sdkを入れてみました。

ところが、初めに作ったサンプルでつまずきました。applicationのaccess tokenを使ってGraph APIを呼ぶのですが、エラーになるのです。うーん、なぜだ、と思ってソースを眺めると、あれ?application access tokenを取得するAPIが想像していたもの(以前、Rubyのライブラリを使ったことがあった)と違います。こりゃ、動かないわけです。もしかしたら、Facebookの仕様が変わったのかもしれません。深追いするのは止めました。たぶん、user access tokenを使っている範囲では正常なのでしょう。

やはり、Star数の多いものから使おうと、Thuzi/facebook-node-sdkとfbgraphを天秤にかけます。README.mdを読んで比較しました。結果、fbgraphにはapplication access tokenを取得する方法が書いてなく、できるとしてもすっきりしないと予想しました。一方、Thuzi/facebook-node-sdkにはapplication access tokenを取得する方法が書いてありました。application access tokenはサーバ側のコーディングのみで使用可能なため、Facebook JavaScript SDKには入っていないnon-standardな機能です。

作ったサンプルも動作し、満足です。application access tokenを使おうとしている人には参考になるかもしれません。ただし、現時点での話であることをお忘れなく。将来的には状況は変わるかもしれません。

参考までに、コードの抜粋はこちらに。