Express4 + Passport で Google アカウントでログインしたセッションを Socket.IO から参照する


ほどよいサンプルがなかったので作った。

## Express4 のインストール

WebStorm 10 のテンプレートで「Node.js Express App」を選んで自動的に作成。
Template は Jade。

モジュールはこんな感じ

```Javascript:package.json
{
  "name": "NodeExpPassportOAuth2_SocketIO",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.4",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "express": "~4.12.4",
    "jade": "~1.9.2",
    "morgan": "~1.5.3",
    "serve-favicon": "~2.2.1",
    "passport": "^0.2.2",
    "express-session": "^1.11.2",
    "mongoose": "^4.0.4",
    "passport-google-oauth": "^0.2.0",
    "socket.io": "^1.3.5",
    "cookie": "^0.1.3",
    "passport.socketio": "^3.5.1",
    "connect-mongo": "^0.8.1"
  },
  "main": "app.js",
  "devDependencies": {},
  "author": "",
  "license": "ISC"
}
```

このような。



## MongoDB を準備する

いろいろ保存しておきたいので MongoDB を使う。
データのモデルは models/user.js に作る

```Javascript:modeles/user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var User = new Schema({
    // User ID (google account id)
    uid: {
        type: String,
        unique: true
    },
    // display name
    displayName: {
        type: String
    },
    // E-mail
    email: {
        type: String
    },
    // icon
    icon: {
        type: String
    }
}, {
    // define this collection's name explicitly
    collection: "users"
});

module.exports = mongoose.model('User', User);

```

```Javascript:app.js

var User = require('./models/user.js');
mongoose.connect('mongodb://localhost/passport-google-oauth-example');
var mongoose = require('mongoose');

```


## Passport で Google アカウントと連携する

passport と passport-google-oauth を使う。

```Javascript:app.js

var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

// session serializer
passport.serializeUser(function(req, uid, done) {
  done(null, uid);
});

// session deserializer
passport.deserializeUser(function(req, uid, done) {
  // MongoDB からユーザーを取ってくる
  User.findOne({
    uid: uid
  }, function(err, user) {
    done(null, user);
  });
});

// Google アカウントで OAuth2 接続
passport.use(new GoogleStrategy({
	  clientID: GOOGLE_CLIENT_ID, //ご自分のやつをどうぞ
	  clientSecret: GOOGLE_CLIENT_SECRET, //ご自分のやつをどうぞ
	  callbackURL: CALLBACK_URL //ご自分のやつをどうぞ
	}, 
	function(accessToken, refreshToken, profile, done) {
	  proce	ss.nextTick(function() {
		//認証情報を DB に保存
	    var uid = profile.id;
	    var displayName = profile.displayName;
	    var email = profile.emails[0].value;
	    var icon = profile.photos[0].value;

	    User.findOneAndUpdate({
	      uid: uid
	    }, {
	      $set: {
	        uid: uid,
	        displayName: displayName,
	        email: email,
	        icon:icon
	      }
	    }, {
	      upsert: true
	    }, function(err, user) {
	      return done(null, uid);
	    });
	  }
	);

}));

```

## SessionStore に MongoDB を使う

connect-mongo が良いらしい。

```Javascript:app.js
var MongoStore = require('connect-mongo')(session); //Express のセッションを引数に渡す
var sessionStore = new MongoStore({ mongooseConnection: mongoose.connection }); 

//Express のセッションを設定
app.use(session({
    key: 'express.sid', //socket.io から参照する際にキーとして使ってるっぽい
    secret: "session_secret",
    saveUninitialized: true,
    store: sessionStore, //passport-socketio のセッションストアに使えるやつじゃないとエラー出る 
    resave: false
}));
app.use(passport.initialize());
app.use(passport.session());

```

## router まわりを設定する

```Javascript:routes/index.js

// Google アカウントでログインするためのリンク
router.get('/auth/google', passport.authenticate('google', {
      scope: ['https://www.googleapis.com/auth/plus.login', "email", "profile"]
    }),
    function(req, res) {} // this never gets called
);

// Google OAuth のコールバックを設定
router.get('/oauth2callback', passport.authenticate('google', {
  successRedirect: '/',
  failureRedirect: '/login'
}));


/* logout */
router.get('/logout', function(req, res) {
  req.logout();
  res.redirect('/');
});

```

## あとは Socket IO から諸々設定するだけだ！

```Javascript

var io = require('socket.io')(server);
var passportSocketIo = require("passport.socketio");

//Passport のセッションを SocketIO からも使えるように
io.use(passportSocketIo.authorize({
    passport : passport,
    cookieParser: require('cookie-parser'),
    key:          'express.sid',
    secret:       'session_secret',
    store:        sessionStore, //さっきの sessionStore 渡す
    success:      onAuthorizeSuccess,
    fail:         onAuthorizeFail
}));


// セッションのAuthorize 成功

function onAuthorizeSuccess(data, accept){
    console.log('successful connection to socket.io');
    accept(null, true);

}

//セッションのAuthorizeしっぱいしっぱい
function onAuthorizeFail(data, message, error, accept){
    if(error) {
        throw new Error(message);
    }
    console.log('failed connection to socket.io:', message);
    accept(null, false);
}

//接続があったら
io.on('connection', function(socket) {
    console.log('socket-io connect');

    var user = socket.request.user; //これでユーザーを参照できる
    if(user){
        console.log("session data : ", user);
    }
});

```

## HTML側から

Google アカウントでログインしたあとに、普通に SocketIO に connect してください。

```Javascript

var socket = io.connect();

```



## ややこしいところ

* Express 3 と 4 で Passport まわりの挙動がいちいち違う。
* Google アカウントの認証モジュールがたくさんあって困る。 しかも先日、 OAuth2しかつながらなくなったので使い物にならないものも多い。 npm のサイトで DL 数の多いやつを選ぶとよいかも。
* 同様に、Socket IO と Passport を繋ぐモジュールがたくさんあって困る。
* Passport の sessionStore では動くけど Socket.IO の sessionStore に指定できないやつが結構ある。
このへん https://github.com/senchalabs/connect/wiki#session-stores を見るといいらしい。