new takyam();

Qiitaぽい話はQiitaに書いていくことにする気がする http://qiita.com/takyam

SocketStreamにeveryauthでTwitterログインさせる

SocketStream にFacebookやTwitterなどのソーシャルアカウントでのログインをする時には、everyauth がオススメです。
ソーシャルログイン系のライブラリでは、他にPassportとかもあるんですが、

Easy authentication - use a backend database or authenticate against Facebook Connect, Twitter, etc using Everyauth

と、socketstreamのReadmeに記載があり、ドキュメントにもEveryauthの使い方が紹介されています。

また、機能的にもeveryauthで特に問題ないので(Passportとの違いがよくわからん)、今回はEveryauthを使ってログインさせてみましょう。

※CoffeeScriptで書いてるので読めない人はコンパイルしてね

app.coffee

※SocketStreamは言語にcoffeeを指定($ socketstream new -c hoge)しても app.js しか出力してくれないので、手作業で app.coffee にデコンパイルしてあります。

#app.coffee
http = require('http')
ss = require('socketstream')
everyauth = require('everyauth')
config = require('config')
mongoose = require('mongoose')

#connect to mongodb
mongoose.connect 'mongodb://localhost/HOGE'

ss.session.store.use('redis')
ss.publish.transport.use('redis')

#Define a single-page client called 'main'
ss.client.define 'main',
  view: 'app.html'
  css:  ['app.less']
  code: ['libs/jquery.min.js', 'libs/underscore-min.js', 'libs/backbone-min.js', 'app']
  tmpl: '*'

#Serve this client on the root URL
ss.http.route '/', (req, res)->
  res.serveClient('main')

User = require('./server/rpc/models/user').User

#twitter oauth
everyauth.twitter
  .consumerKey(config.Twitter.consumerKey)
  .consumerSecret(config.Twitter.consumerSecret)
  .findOrCreateUser((session, accessToken, accessTokenSecret, twitterUserMetaData)->
    #str_id でユーザーを検索
    User.findOne({id_str: twitterUserMetaData.id_str}, (err, user)->

      #create new user
      user = new User id_str: twitterUserMetaData.id_str if !user

      #create or update user name and icon
      user.name = twitterUserMetaData.screen_name
      user.icon - twitterUserMetaData.profile_image_url

      #save user
      user.save()

      session.user = user.toObject()
      session.save()

      true
    )
    true
  )
  .redirectPath('/')

#Code Formatters
ss.client.formatters.add(require('ss-coffee'))
ss.client.formatters.add(require('ss-less'))

#Use server-side compiled Hogan (Mustache) templates. Others engines available
ss.client.templateEngine.use(require('ss-hogan'))

#Minimize and pack assets if you type: SS_ENV=production node app.js
if ss.env is 'production'
  ss.client.packAssets()

#for everyauth
ss.http.middleware.prepend(ss.http.connect.bodyParser())
ss.http.middleware.append(everyauth.middleware())

#Start web server
server = http.Server(ss.http.middleware)
server.listen(3000)

#Start SocketStream
ss.start(server)

まず、everyauthをrequireします。

everyauth = require('everyauth')

当然、「$ npm install everyauth」や package.json に書いておく必要があります。

次に、順番は前後しちゃいますが、以下が結構重要です。

#for everyauth
ss.http.middleware.prepend(ss.http.connect.bodyParser())
ss.http.middleware.append(everyauth.middleware())

bodyParserは、connectbodyParser で、レスポンスボディをパースしてくれるみたいです。
たぶんcallback時のURLをパースしてモニョってくれるんだと思います。
まずはそれを prepend に指定する必要があると。

で、 apeend にeveryauth.middleware() を指定する事で、認証用のURLが有効になります。

everyauth.twitter
  .consumerKey(config.Twitter.consumerKey)
  .consumerSecret(config.Twitter.consumerSecret)
  .findOrCreateUser((session, accessToken, accessTokenSecret, twitterUserMetaData)->
    #(略
  )
  .redirectPath('/')

最後にこの部分で、twitterによる認証処理をモニョモニョします。
モニョモニョっていっても、consumerKey / consumerSecretKey を指定するのと、oauth認証ができたあとのcallback処理を書く感じです。

最初に書いた例では、mongoose(MongoDBのORM的なの)のUserモデルからid_strが一致するユーザがいるかどうかを確認して、いれば名前とアイコンをアップデート、新規ユーザであれば新しくオブジェクトをMongoDBに保存しています。
そのうえで、セッションにユーザデータを保存しているという感じです。

rpc内での利用

app.coffee で認証とセッションデータの作成だけやっておいて、rpc内で使う時は、

#server/rpc/user.coffee
exports.actions = (req, res, ss) ->
  req.use('session')
  get_name: =>
    res req.session.user.name

みたいな感じで、req.user('session') しておけば、各アクションの中で req.session で参照する事ができます。

クライアントサイドからの呼び出し

クライアントサイドで、

ss.rpc 'user.get_name', (user_name) ->
  console.log(user_name)

みたいな感じで呼び出せば、ユーザ情報をクライアントサイドへ渡す事ができます。

ログインURL

everyauthの仕様で、ログインURLは決まっています。
もちろんオプションで変更させる事もできるのですが。

#everyauthのreadmeに載ってるログインURLとコールバックURLの変更(facebookの場合)
everyauth.facebook
  .entryPath('/auth/facebook') #<-認証URL
  .callbackPath('/auth/facebook/callback')

ようするに、http://example.com/auth/ソーシャルサービス名 がログインURLで、その後に /callback を足したものがコールバックURLとしてデフォルトで設定されています。

なので、/auth/twitter に対してリンクを貼れば、そこがTwitterログインのURLになる、といった感じです。

知らないと、「everyauth設定したけど結局どこでログインすりゃいいのさ!?」ってハマっちゃいます(体験談

まとめ

HTTPのセッションとWebsocketのセッションは本来別物なのですが、そこはSocketStreamが良い感じにしてくれますし、OAuthについてはトークンを設定する以上の事は何もせずともeveryauthがよしなにやってくれますし、TwitterとFacebookとなんとかと、、、と、複数のログイン手段を、超簡単に用意する事ができます。

SocketStreamの日本語情報が世の中に少なかったりするので、ちょいちょい書いていこうかなと思い、第1弾としてeveryauthとの連携を紹介しました。