PassportによるFacebook認証(続いてDBへ保存)

Passportでの認証を実践的に進めてみます。認証情報を永続化するのにMongo DBを利用します。はじめだけMongo DBのJSドライバをそのまま使って書いてましたが、コードブロックをtry-finallyで囲って最後にDB接続を解放するような書き方など2000年ぐらいにJava出始めでJDBCで接続ガーと言ってる頃を思い出しました。懐かしいのは良いことばかりでもなく、何事もイージーにライトでやりたい現代のスピード感にそぐわないのですぐにMongoose通じてアクセスするように。Mongooseを選んだのは単に軽くググったらたくさん記事が出てきたのでメジャー感あったためです。アーキテクチャはいわゆるActive Record的なアレですね。機能は十分に豊富ですが、とは言えシンプルです。

var Mongoose = require('mongoose');
Mongoose.connect('mongodb://localhost:27017/buzzw');
var Account = Mongoose.model('accounts', new Mongoose.Schema({
    provider: String,
    id: String,
    displayName: String,
    accessToken: String,
    lastLogin: Date
}));

var Passport = require('passport');
var Facebook = require('passport-facebook');
Passport.use('fb', new Facebook.Strategy({
        clientID: '1513567018936647',
        clientSecret: '2596-xxxxxxxxxsecretxxxxxxx-d126',
        callbackURL: "http://localhost:8080/auth/facebook/callback"
    },
    function(accessToken, refreshToken, profile, done) {
        Account.findOne({id: profile.id}, function(err, account) {
            if(err) {
                return done(err);
            }
            if(!account) {
                // Active Record的なオブジェクトのコンストラクタにドメインオブジェクトを渡す
                account = new Account(profile);
            }
            account.accessToken = accessToken;
            account.lastLogin = new Date();
            account.save(function(err) {
                return done(err, account);
            });
        });
    }
));

Passport.serializeUser(function(user, done) {
    done(null, user._id);
});

Passport.deserializeUser(function(obj, done) {
    Account.findOne({_id: obj}, function(err, account) {
        done(err, account);
    });
});

var Express = require('express');
var app = Express();
app.disable('x-powered-by');
app.use(Passport.initialize());
app.use(Passport.session());
app.get('/auth/facebook', Passport.authenticate('fb'));
app.get('/auth/facebook/callback', Passport.authenticate('fb',
    {failureRedirect: '/index.html', successRedirect: '/success.html'}));
app.use(Express.static('public'));
app.listen(8080);

Active Record的なオブジェクトを作るのにSchemaを引数に渡しますが、こちらはnew付きの呼び出ししか対応していなかったので注意。最近の多くはnewをつけてもつけなくても同じ動きをするように一手間費やされているライブラリが多いのですが、ここにはそれがなく。 新規時にこのオブジェクトを作る時、つまりレコードをinsertする時にはFacebookから得られたドメインオブジェクトたるユーザー情報(profile)を渡してあげると、そのまま綺麗にDBへ格納してくれます。insertもupdateもコード的には区別なくいい感じ。

Mongooseでちょっと気持ち悪かったのは、connectで返すのがDBインスタンスではなくSocketで、インスタンスはMongoose.connectionというプロパティに入ってるってことかな。例外処理はDomain使った風に以下のように宣言します。

var db = Mongoose.connection;
db.on('error', function(err) {
    console.error(err);
});

APIユースケースとしては、connect呼び出し時にインスタンス返せばいいのにね。