カテゴリー別アーカイブ: express

Node.js express 4.x系のセッション管理

express 4.x系ではセッションのモジュールも分離されているので, 別途インストールが必要になります.

$ (sudo) npm install (-g) express-session

そして, app.jsでexpress-sessionを読み込みます.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var session = require('express-session');
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
     secret : 'hogehoge',
     resave : true,
     saveUninitialized : true,
}));
app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

使い方としては, それほど難しくないですね.

また, セッションストアを利用する場合は以下のような感じになります (以下のサンプルコードでは, MongoStoreを利用しています).

MongoStoreのインストール

$ (sudo) npm install (-g) connect-mongo
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
     secret : 'hogehoge',
     resave : true,
     saveUninitialized : true,
     store : new MongoStore({
         db : /* データベース名 */,
         host : /* ホスト名 */,
         port : /* ポート番号 */
      }),
      cookie : {
          path : '/',
          secure : false,
          httpOnly : true
      }
}));
app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

注意が必要なのは, セッションストアのコンストラクタの引数です. 3.x系までは, この引数にはexpress関数を渡していましたが, 4.x系では, express-sessionを渡す必要があります.

以外と見落としやすいので, ハマってしまわないように注意してください…

Node.js express 4.x系のスケルトンコード

まずは, expressとexpress-generatorをインストールしておきます.

$ (sudo) npm install (-g) express(@4)
$ (sudo) npm install (-g) express-generator

スケルトンコード用のディレクトリも作成して移動しておきましょう. ディレクトリ名は手キトーで構いません.

$ mkdir app
$ cd app

expressコマンドを実行すればファイルが生成されます.

 

$ express

   create : .
   create : ./package.json
   create : ./app.js
   create : ./public
   create : ./public/images
   create : ./public/stylesheets
   create : ./public/stylesheets/style.css
   create : ./routes
   create : ./routes/index.js
   create : ./routes/users.js
   create : ./views
   create : ./views/index.jade
   create : ./views/layout.jade
   create : ./views/error.jade
   create : ./bin
   create : ./bin/www
   create : ./public/javascripts

   install dependencies:
     $ cd . && npm install

   run the app:
     $ DEBUG=app ./bin/www

これだけだと, 肝心のnode_modulesがインストールされていないので, 以下のコマンドを実行します.

$ (sudo) npm install

express 3.x系だと, ここまでできれば,

$ node app.js

これで, スケルトンコードが動作したのですが, 4.x系では動作しません.

4.x系でスケルトンコードを動作させるには,

$ node ./bin/www

と実行する必要があります.

デフォルトのポートは3000番なので, 例えば, http://localhost:3000/ にアクセスすると,

Express

Express

Welcome to Express

 

のように表示されます.

ちなみに, 4.x系のスケルトンコード (app.js) は以下のようになっています.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

パッと見た感じでも, 3.x系までとはずいぶん違います. 最大の違いは,

  • 4.x系からはモジュールの分離が進んでいるので, 多くのモジュールをrequireしている
  • ルーティングの定義が別ファイル (routes/index.js) に移動している

ちなみに, 4.x系のルーティングは以下のようになります (スケルトンコード routes/index.jsから抜粋)

 

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

と. このようにルーティングの実装もずいぶん変わりました.

Node.js express 4.x系のインストール

express 4.x系からはモジュールの分離化がかなり進められているので, 3.xまでとはインストール方法から異なってきます.

ちなみに, 3.x系までは,

$ (sudo) npm install (-g) express

これだけで, expressコマンドがインストールされ, expressコマンドを実行すると, スケルトンコードが生成されました.

4.x系では, express自体のインストールは同じです.

$ (sudo) npm install (-g) express(@4)

ところが, expressインストールだけではexpressコマンドはインストールされないので, スケルトンコードの生成ができません.

expressコマンドを利用可能にするには, 追加でexpress-generatorをインストールする必要があります.

$ (sudo) npm install (-g) express-generator

これで, expressコマンドが利用可能になります.
確認として, バージョンを表示します.

$ express -V
4.11.1

expressでセッション管理の注意点

Node.jsに限らず, セッションを扱うWebアプリでは, セッションハイジャックなどの対策のために, ログイン後などにセッションIDを再生成する.

例えば, PHPであれば,

session_start();

$oldId = $_COOKIE['PHPSESSID'];

//ログイン成功 ...

session_regenerate_id(true);

$newId = $_COOKIE['PHPSESSID'];

//$oldId !== $newId のはず

Node.jsのフレームワークexpressでも同じことが可能.

ルーティングされているコントローラの引数に渡される, Http.ServerRequestオブジェクトのsessionプロパティのregenerateメソッドを利用する.

var sessionRegerateId = function(req, res) {
    req.session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

        //セッションID再生成後の処理
    });
};

ただし, PHPのsession_regenerate_idと違って, IDだけを再生成して, セッション変数に格納した値を保持しているという実装にはなっていない感じで, regenerateメソッドによって, 新しいsessionオブジェクトを生成するという実装になっているっぽいので, regenerateメソッドを実行すると, セッションに格納していた値は新しいセッションオブジェクトには引き継がれなくなる.

したがって, 以下のようにセッションオブジェクトを別変数に格納して, 参照を残していると, 古いセッションオブジェクトを参照してしまうことになる.

var sessionRegerateId = function(req, res) {
    var session = req.session;
    session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

        //古いセッションオブジェクトを参照したまま !!
        session['is_auth'] = true;
    });
};

まとめると, 以下のようにすればとりあえずOK (?) と思います.

var sessionRegerateId = function(req, res) {
    var sessions = {}

    //セッションに格納している値を取得しておく
    for (var key in req.session) {
        sessions[key] = req.session[key];
    }
    req.session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

       //セッションIDが再生成, すなわち, 新しいsessionオブジェクトが生成されたので, セッションに格納していた値を復元する
        for (var key in sessions) {
            req.session[key] = sessions[key];
        }
        //セッションID再生成後の処理
    });
};

自分はこれにかなりはまってしまったので, 大変でした…
ぜひ気をつけてください.

Jade

Jadeテンプレートに渡す変数は勝手にエスケープ (PHPでいうところの, htmlspecialchars) が適用される. これで, XSS対策のし忘れはないけれども, HTML文字列を出力したいって場合のためのオプションとかないのかな … ? と調べてみた限りではなさそう.

なので, その場合には元の文字列に戻す処理を自前で実装する…

example.

JSONデータをpostMessageする子iframeのJadeテンプレート

doctype html
html(lang='ja')
    head
    title postMessage
    meta(charset='UTF-8')
    script(type='text/javascript') window.onload = function()   {window.parent.postMessage('#{data}', '*');};
    body

postMessageを受ける親フレーム

window.onmessage = function(event){
    //エスケープされたままJSON.parse()するとエラーなので…
    //もとに戻す
    var json = event.data.replace(/"/g, '"')
                                           .replace(/&lt;/g, '<')
                                           .replace(/&gt;/g, '>')
                                           .replace(/&amp;/g, '&');
    var datas = JSON.parse(json);
};

まあ, Jadeはいわゆるフェールセーフな設計になっているということでしょう (危険な場合でも安全な方向に働くような設計).

 

Node.js express

expressのresponseオブジェクトのrenderメソッドにコールバック関数を指定すると, ブラウザの出力やAjaxのレスポンスとして返さずに, テンプレート出力したHTMLの文字列が格納される.

function(req, res){
    //Not output
    res.render('template', {title : 'sample'}, function(error, html) {
        /* do something ...*/
}
}

これを利用すれば, window.postMessageを利用したクロスオリジン対応の (擬似的な) Ajaxにも利用できる.

function(req, res){
    //まずは, レスポンスとして返したい, HTMLを生成
    res.render('template', {title : 'sample'}, function(error, html) {
        //Output to iframe -> postMessage -> 'onmessage' event in parent frame
        res('iframe', {data : html});
}
}

expressでセッション管理

セッションストアとして, MongoStoreを使うとして…

まずは, インストール

$ sudo npm install -g connect-mongo
var express        = require('express');
var MongoStore = require('connect-mongo')(express);

var app = express();

app.configure(function(){
    app.use(express.cookieParser());  //Cookieを使うので必要
    app.use(express.session({
        /*オプションの詳細はこちらを参照*/
        key      : 'session',  //Session IDのキー
        secret : 'secret',     //署名つきCookieのパスフレーズ
        store   : new MongoStore({
             /*その他のオプションはこちらを参照*/
             db     : 'database-session'  //データベース名
        }),
        cookie : {
            /*その他のオプションはこちらを参照*/
            maxAge : new Date(Date.now() + 3600000)  //1 hours  (Dateインスタンスを指定するので注意)
        }
 }));
});

これで, セッション管理をする準備は完了です.

(connect-mongodbを使う場合はまた異なるので, くれぐれも注意してください…)

セッションストアには, MongoStore以外にも, KVSなら, redisやmemcached, RDBなら, MySQLやPostgreなどが使えます.