express-sessionがバージョンアップしているせいか、ネット上のサンプル見て実装してたらハマったのでメモ。
作るもの
- express-generatorで作成した雛形をベースにする
- ユーザ認証はしない(別の機会に)
- セッションストアはひとまずメモリストア(デフォルト、再起動で初期化)
- アプリケーションルートにアクセスした時に、ユーザ名がセッションに無かったらログイン画面(というかユーザ名入力画面)にリダイレクトさせる
- ユーザ名が入力されたらセッションに格納し、アプリケーションルートにリダイレクトさせる
SPAなWEBアプリケーションにユーザ入力を挟むようなルーティングのサンプルです。
出来上がったもの
githubにあげておきました。
このコミットでの差分を見れば作業内容は把握できるかと思います。
作業の手順
express
コマンドで雛形作成
$ express [アプリケーション名]
サンプルで--hbs
とか--ejs
とかしていないので、そういう方は適宜読み替えてください。
あまり重要な部分ではないです。
app.jsを修正
app.jsの修正が一番手間な部分です。
モジュールの読み込み
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session'); // 追加
express-session
を読み込みます。
ルーティング定義ファイルを読み込み
var routes = require('./routes/index');
var users = require('./routes/users');
var login = require('./routes/login'); // 追加
ルーティングを実装する予定のroutes/login.js
を読み込みます。
express-sessionモジュールを設定
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 以下追加
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 30 * 60 * 1000
}
}));
session(=express-session)を利用する設定をします。
secret
はCookieの暗号化に利用するキーなので適宜変更して利用します。アプリケーションリリース後に変更したい場合は、
secret: ['(新しいキー)', '(今までのキー)', '(今までのキー)']
とすることで先頭のキーを暗号化に用いて、復号化には配列中のいずれかのキーを利用するらしいです(未検証)。
resave
はtrueだとセッションストアにアクセスするたび(=セッションチェックする領域にリクエストするたび)にセッションを作り直すらしいので、基本はfalseでOKかと思います。
saveUninitialized
はtrueだと未初期化状態のセッションも保存されるようになるようですが、一般的に使われるログインセッションの処理では未初期化のセッションは不要なので、falseでOKかと思います。
resaveとsaveUninitializedの挙動は今ひとつ自信がない……。
cookie.maxAge
はCookieの有効期限をミリ秒で設定します。指定なし、もしくはnullだとブラウザデフォルトの挙動(一般的にはブラウザを閉じたらCookie削除)になります。
セッションチェック処理を実装
この部分はmiddlewareとして別ファイルに分割した方がスマートかもですね。
var sessionCheck = function(req, res, next) {
if (req.session.user) {
next();
} else {
res.redirect('/login');
}
};
セッション情報(req.session.*
、今回はreq.session.user
に格納されている)の有無をチェックして後処理に継続next()
するか、ログイン画面にリダイレクトするか分岐させています。
ルーティングの追加とセッションチェック処理の追加
app.use('/login', login); // 追加
app.use('/', sessionCheck, routes); // sessionCheckを前処理に追加
app.use('/users', users);
app.use([path], function[, function...])
のpathの部分は指定したパス以下に適用してしまうため、app.use('/login', login);
はapp.use('/', sessionCheck, routes);
よりも前に追加しないと、sessionCheckが適用されてしまい永遠にログイン画面にリダイレクトが発生してエラーになります。
ログイン処理のルーティングを定義
app.use('/login', login);
から呼ばれているので'/'が'/login'を表すことになります。
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.render('login');
});
router.post('/', function(req, res, next) {
if(req.body.userName) {
req.session.user = {name: req.body.userName};
res.redirect('../');
} else {
var err = '入力が正しくありません。確認して再入力してください。';
res.render('login', {error: err});
}
});
module.exports = router;
router.get('/', ...)
の部分はログイン画面('/login')へのアクセスなので単純にlogin.jadeを表示するだけです。
router.post('/', ...)
の部分はログイン画面のFORMからPOSTされてアクセスされます。
ここではreq.body.userName
の有無のみで分岐していますが、パスワード認証の場合はreq.body.password
なども参照してDB参照した上で分岐になります。
req.session
以下にデータを格納することでセッションへのデータ格納が実現できます。
入力データが正しくない場合はエラーメッセージを用意してlogin.jadeに渡します。
ログイン画面の実装
エラー表示と/loginへのPOSTのみなので参考までに。
extends layout
block content
h1 Login
p.caution= error
form(method='POST',action='/login')
input(type='text',name='userName',placeholder='username')
input(type='submit',value='Login')
パスワード認証の場合は
input(type='password',name='password')
などを適宜追加してください。
セッションストアはメモリストア以外を使ったほうが実用的と思うので、大規模システムならredisなど、スケール考えつつ手軽に始めるならnedb(mongo互換のファイルDB)などにしておいたほうが良いと思います。
「express-sessionモジュールを設定」の部分で設定追加することで出来るので、気が向いたらまた記事にします。