pig's diary

何でも忘れるので万年初心者ね

expressでOAuth。Twitterに投稿。

なんだかよく分からなかった。やっとできた。基本的なことが分かってないんだね。

追)githubにサンプルを作りました https://github.com/piglovesyou/express-twitter-oauth-sample

やること:

  1. 下準備。ライブラリの準備。
  2. app.get('/', routes.index);。ログインしてなかったら、/login にリダイレクト。
  3. app.get('/login', routes.login); ログイン画面。ユーザーに「Twitterでログイン」をクリックしてもらう。
  4. app.get('/auth/twitter', routes.auth.twitter); request_token を取得。取得したら、authenticate を叩く。
  5. app.get('/auth/twitter/callback', routes.auth.twitter.callback); verifierを取得。取得したら、access_tokenを取得。
  6. 準備完了。ユーザーのscreen_name を出して、ログイン中画面にユーザーをお招き。
  7. app.post('/post', routes.post); アプリ経由でtweet を投稿してみる。
  8. app.get('/logout', routes.logout); ログアウト。セッションを破壊。大爆発。

1. 下準備。ライブラリの準備。

https://dev.twitter.com/appsで、アプリを登録。今回はtweetしたいので、read and write に設定しておく。
expressは、sessionを使うオプションでプロジェクトを作成。
npm install oauth しておいて、./routes/index.js で読みこむ。 ./routes/index.js の先頭で、oauth の設定をする(インスタンス作り)。

// ./routes/index.js
var OAuth       = require('oauth').OAuth;
var oa = new OAuth(
  "https://twitter.com/oauth/request_token",
  "https://twitter.com/oauth/access_token", 
  "Your consumer key is here.",
  "Your consumer secret is here.",
  "1.0",
  "http://localhost.com:3000/auth/twitter/callback",
  "HMAC-SHA1");

これでTwitterアプリ用のoauthインスタンスができた。

2. app.get('/', routes.index);

このページは、ログインユーザーしか見れなくする。
ログインしてる人のために、テンプレート「index」が必要。
ログインしてない人は、ただちに /login に飛んでもらう。

exports.index = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    res.render('index', {
      screen_name: req.session.twitter.screen_name
    });
  } else {
    res.redirect("/login");
  }
};

3. app.get('/login', routes.login);

ログインしてない人が、「ログイン」リンクをクリックしてもらうためのページ。無くてもいい。ここに「Twitterでログイン」「Facebookでログイン」の2つをあとで置こうかなと思ってます。今は、Twitterだけ。
テンプレート「login」が要ります。

exports.login = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
  } else {
    res.render('login');
  }
};

4. app.get('/auth/twitter', routes.auth.twitter);

認証のためのURL。テンプレートはなし。アクセスと同時に、oauthインスタンスにrequest_tokenを取りに行かせる。
request_tokenをtwitterにもらったら、ユーザーにそれを握らせて、アプリの認証画面に行ってもらう。あと、貰ったほかのものはsessionにしまっておく。ユーザーが「許可」してくれることを祈って待つ。

exports.auth = {};
exports.auth.twitter = function(req, res){
  oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){
    if (error) {
      res.send("yeah no. didn't work.")
    } else {
      req.session.oauth = {};
      req.session.oauth.token = oauth_token;
      req.session.oauth.token_secret = oauth_token_secret;
      res.redirect('https://twitter.com/oauth/authenticate?oauth_token='+oauth_token)
    }
  });
};

5. app.get('/auth/twitter/callback', routes.auth.twitter.callback);

OAuthをnew したところと同じcallback用のURL。これはTwitterのアプリ設定画面でも同じものを登録しとく必要がある。
ユーザーが「許可します」ボタンを押してくれたら、Twitterはユーザーをここに連れてくる。パラメータには、ユーザーからのOKサインである「verifier」が入っているので、ありがたく貰っておく。
verifierと、さっきsessionにしまったものをきっちり揃えて、Twitterにお願いしに行く。書類をそろえて、「このユーザーと懇意にさせてください」とお願いする。書類に不備がなければ、OKのハンコがもらえる。OKハンコは「access_token」と「access_token_secret」の2つ。これがずっと欲しかったもの。目的を達成したから、それをsessionに大事にしまって、ユーザーをログイン中画面に連れていく。

exports.auth.twitter.callback = function(req, res, next){
  if (req.session.oauth) {
    req.session.oauth.verifier = req.query.oauth_verifier;
    var oauth = req.session.oauth;
    oa.getOAuthAccessToken(oauth.token, oauth.token_secret, oauth.verifier, 
        function(error, oauth_access_token, oauth_access_token_secret, results){
          if (error){
            res.send("yeah something broke.");
          } else {
            req.session.oauth.access_token = oauth_access_token;
            req.session.oauth.access_token_secret = oauth_access_token_secret;
            req.session.twitter = results;
            res.redirect("/");
          }
        }
    );
  } else
    next(new Error("you're not supposed to be here."));
};

6. 準備完了。

ユーザーのscreen_name を出して、「ログイン中画面」にユーザーをお招きする。

exports.index = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    // コッチ。
    res.render('index', {
      screen_name: req.session.twitter.screen_name
    , title: 'Express'
    });
  } else {
    res.redirect("/login");
  }
};

これで「Twitterでログイン」ができました。

7. app.post('/post', routes.post);

このアプリ経由で、Tweetしてもらう。
ここは、xhrで叩く想定。ユーザーがここにPOSTすると、textパラメータの値をTwitterに投稿するしくみにする。

クライアントサイドのHTMLとJSはこんな。さきにjQueryを読んでおく。

// index.jade
form#tweet
  input(type="text", name="text")
  input(type="submit", value="ツイート。")
// client side script
$('#tweet').submit(function (e) {
  var text = e.target.text.value;
  $.ajax({
    url: '/post'
  , type: 'POST'
  , data: {text: text}
  });
  return false;
});

うん。
nodeは、リクエストを受け取ると、こんどはoauthインスタンスにリクエストを投げさせる。引数に、TwitterのOKハンコであるaccess_tokenとaccess_token_secretを渡す(ユーザ特定のヒントとユーザ許可済みのしるし)。oauthインスタインスは、Twitterに特別なリクエストを投げてくれる。
特別っていうか、セッションに保存しているtokenやらいろいろを使って、特別なヘッダを作って、リクエストを飛ばす。
このときどんなリクエストを投げて欲しいかを、Twitterは丁寧に解説している。

... At a very simplified level, Twitter's implementation requires that requests needing authorization contain an additional HTTP Authorization header with enough information to answer the questions listed above. ...

POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com

status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

https://dev.twitter.com/docs/auth/authorizing-request

consumer_keyやら、access_tokenやら。もう必要なものは全部持っているんだけど、この特別なヘッダ(Authorization header)を作るのが大変。いろいろ混ぜたりこねたりしなくちゃいけない。
これをやってくれるのが、node-oauthモジュール。node-oauthの中身をのぞくと、たとえばaccess_token_secretを使って、まぜたりこねたりして、シグネチャーを作っているのが分かる。絶対に作りたくないね。それをやってくれる。ありがたいね、本当の話。

exports.post = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    var text = req.body.text;
    oa.post(
      'https://api.twitter.com/1/statuses/update.json',
      req.session.oauth.access_token, 
      req.session.oauth.access_token_secret,
      {"status": text},
      function (err, data, response) {
        if (err) {
          res.send('too bad.' + JSON.stringify(err));
        } else {
          res.send('posted successfully...!');
        }
      });
  } else {
    res.send('fail.');
  }
};

登録しているアプリ経由で、投稿できたはず。

8. app.get('/logout', routes.logout);

ログアウトする。
ログアウトのリンクをどっかに置いておいて、テンプレート「logout」も作っておく。テンプレートには「完全にログアウトしたから安心するように」と書いておく。

exports.logout = function (req, res) {
  req.session.destroy();
  res.render('logout');
};

ボンッッ。おしまい。


参考: