facebook-node-sdk の初期化より先に session を有効化しないといけない

node.js で Facebook API 使うときに facebook-node-sdk つかってるんだけど、 loginRequired() の middleware つかったときに、アプリ新規承認時に redirect_uri に無限ループするということがあったりした。

で、出てたログが

DEBUG: DEBUG: CSRF state token does not match one provided.

で、redirect 前に設定してた CSRF Token が一致してないとのこと。
で、これがどこででてるかというと、

BaseFacebook.prototype.getCode = function() {
  var code = this.getRequestParam('code');
  if (code !== null) {
    var state = this.getRequestParam('state');
    if (this.state !== null && state !== null && this.state === state) {

      // CSRF state has done its job, so clear it
      this.state = null;
      this.clearPersistentData('state');
      return code;
    } else {
      this.errorLog('CSRF state token does not match one provided.');
      return false;
    }    
  }
  return false;
};

ここで、つまり、state がないか一致しないときに出てる。考えられる原因は、redirect 前にセットしてる state が set できてないか、戻ってきた時に違う state が入ってる or セットしたはずの state がとれてない、とかそういうかんじだとおもうんだけど、このときの state と this.state がどうなってるかというと (console.log !!)、

this.state: null
state: 5GluXPtLoAzcvSYMJ1JU6Mm52J5DKd2u

つまり、

BaseFacebook.prototype.establishCSRFTokenState = function() {
  if (this.state === null) {
    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var buf = [];
    for (var i = 0; i < 32; i++) {
      buf.push(chars[Math.floor(chars.length * Math.random())]);
    }
    this.state = buf.join('');
    this.setPersistentData('state', this.state);
  }
};

でセットされてるはずの state が無い。このロジック自体はあまりおかしくないかんじがする。 setPersistentData はというと、

Facebook.prototype.setPersistentData = function(key, value) {
  if (this.hasSession) {
    this.request.session[key] = value;
  }
};

ということになってて、この hasSession が false になってた。あれ、session つかってるだけど、

function Facebook(config) {
  this.hasSession = !!(config.request && config.request.session);
  BaseFacebook.apply(this, arguments);
}

で、中身をみてみると、 config.request はあるけど、 config.request.session がない。

app.js を見てみると、

   app.use(facebook.middleware({appId: config.facebook.appId, secret: config.facebook.secret}));
   app.use(express.session({ secret: config.session.secret }));

あー、ってことで、

   app.use(express.session({ secret: config.session.secret }));
   app.use(facebook.middleware({appId: config.facebook.appId, secret: config.facebook.secret}));

これで、治った。


ちなみに、なんでアプリ新規承認時のみ発生していたかというと、承認済みの場合、JavaScript SDK のほうの cookie でログインが完了しちゃってたからでした。