パンジェンシーの「汗だく開発日誌」

システム開発の備忘録です。

【Node.js】【MongoDB】express-sessionでログイン機能を実装

こんにちわ!
パンジェンシーです。

f:id:x-fieldatts:20150502134035p:plain

前回は、Node.jsでexpress-generatorを使い、ウェブアプリのひな形を作りました。

pangency.hatenablog.com

今回は、ほとんどのウェブサービスで必要になるであろう「ログイン機能」を実装してみたいと思います。

今回の記事を作成するにあたり、色々なページを参考にさせていただきました。心より感謝致します。あまりに数が多いため、誠に失礼ながら紹介は割愛させていただきます。大変申し訳ございません。

 

MEANスタック

あまり聞き覚えのない言葉ですが、今回実装しているアプリはほぼこの環境を使っています。


現在、Webアプリの開発環境といえば、"LAMP"という、

を使ったものが主流だそうです。


MEANスタックというのは、

  • MongoDB:データベース
  • Express:サーバーサイドアプリのMVC(Model-View-Controller)フレームワーク
  • AngularJS:クライアントサイドアプリのMVW(Model-View-Whatever)フレームワーク
  • Node.js:アプリ実行環境

の組み合わせのことを言うそうです。

こっちは、クライアントサイドもサーバーサイドも全部Javascriptで完結するということで、非常に手軽にWebアプリが作れる組み合わせとして注目を集めているようです。

今回は、クライアントサイドのAngularJSは使っていませんが、ほぼこのMEANスタックの環境と言えます。

クライアントサイドのフレームワークは、AngularJS以外にも色々あるので、自分が実装するアプリに適当なものを選ぶ必要があるかと思います。

 

環境

開発環境は以下のような感じです。

  • Ubuntu 14.04 LTS
  • node.js v0.10.37
  • MongoDB 2.4.9

 

ログイン機能を作ってみよう

前置きが長くなりましたが、アプリを作ってみましょう。


まずは、前回と同様に適当なディレクトリを作り、express-generatorでひな形を作成します。

ここでは、ディレクトリ名:sample-loginとしました。

また、express -eとして、HTMLテンプレートとしてejsを使っています。(何も指定しないとjadeを使用する設定になります。)

$ mkdir sample-login
$ cd sample-login
$ express -e
$ npm install


では、ひな形をいじっていきましょう。

まずは、app.jsを開いてみます。

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エンジンの設定
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// モジュールの適用
// 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;
  • モジュールの読み込み
  • ルーターの読み込み
  • サーバーオブジェクトの作成
  • Viewエンジンの設定
  • モジュールの適用
  • ルーターの適用
  • 例外処理
  • サーバーオブジェクトのエクスポート

という感じの順番で書いてあると思います(私の理解では

 

モジュールのインストールと読み込み

まず、使用するモジュールをインストールします。

今回は、

express-session セッション管理を行うモジュール
connect-mongo セッション管理にMongoDBを使うためのモジュール(?)
mongoose MongoDBを使うためのモジュール

を使用します。

npmでそれぞれインストールしましょう。

$ sudo npm install express-session --save
$ sudo npm install connect-mongo --save
$ sudo npm install mongoose --save

--saveオブションをつけると、package.jsonにモジュールが追加されるので、あとでプロジェクトをコピーしたりするときに便利です。


次に、モジュールを読み込みます。

app.jsを開き、var app = express();の上あたりに以下の文を追加します。

app.js

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

mongooseは、データベースの設定のところで読み込むのでとりあえず放置。

 

セッション管理の設定

次に、セッション管理の設定をします。

セッション管理というのは、要するにユーザーから何回もリクエストが来た時に、このリクエストとこのリクエストは同じユーザーからのものだよっていうのをわかるようにする仕組みのことです(私の理解では)。

具体的には、セッションIDという値をユーザー毎に割り振ることで、同じセッションIDを持っている人は同じユーザーだよというのがわかるようにしています。


今回は、サーバー側でセッションIDを生成してMongoDBに保存、クライアント側はセッションIDをクッキーに保存するように設定します。

app.jsの"app.use('/',routes);"の上あたりに、以下の文を追加します。

app.js

app.use(session({
    secret: 'secret',
    store: new MongoStore({
        db: 'sample-login', // データベース名
        host: 'localhost', // データベースのアドレス
        clear_interval: 60 * 60 // 保存期間(sec)
    }),
    cookie: {
        httpOnly: true, // cookieへのアクセスをHTTPのみに制限
        maxAge: 60 * 60 * 1000 // クッキーの有効期限(msec)
    }
}));

これでとりあえず、セッション管理はexpress-sessionがやってくれるようになります。

 

データベースの設定

次に、MongoDBの設定をします。

まず、UbuntuにMongoDBをインストールします。

$ sudo apt-get install mongodb

mongoと打って、mongoシェルが起動すればOKです。


次に、データベースを使用するために、モデルを定義します。

app.jsと同じディレクトリに、model.jsを作成し、以下のように書きます。

model.js

var mongoose = require('mongoose');
var url = 'mongodb://localhost/sample-login';
var db  = mongoose.createConnection(url, function(err, res){
    if(err){
        console.log('Error connected: ' + url + ' - ' + err);
    }else{
        console.log('Success connected: ' + url);
    }
});

// Modelの定義
var UserSchema = new mongoose.Schema({
    email    : String,
    password  : String
},{collection: 'info'});

exports.User = db.model('User', UserSchema);

ここでは、ユーザーの登録情報を保存するデータベースを定義しており、Emailアドレスとパスワードが保存されます。(パスワードを平文で保存するのはセキュリティ上よくないので、本当はパスワードのハッシュ値などを保存しますが、その方法は次回に書きます

ここで定義したモデルは、この後のルーターのところで使用します。

 

ルーターの作成

次に、ルーターを追加していきます。

ルーターというのは、要するにユーザーから何かリクエストが来た時に、どんな応答をするかの処理を書いたモジュールで、routesディレクトリに格納していくとよいです。


ひな形では、"routes/index.js"に、"127.0.0.1:3000"にリクエストが来た時の処理、"routes/users"に、"127.0.0.1:3000/users"にリクエストが来た時の処理が書いてあります。


app.jsの真ん中あたりにapp.use('/',routes);と書いてありますが、ここでルーターをサーバーに適用しています(多分)。

今回は、usersは要らないので削除して、以下のように書き換えます。

app.jsルーターの読み込み)

var routes = require('./routes/index');
var login = require('./routes/login');
var add = require('./routes/add');
var logout = require('./routes/logout');

app.jsルーターの適用)

app.use('/', routes);
app.use('/login', login);
app.use('/add', add);
app.use('/logout', logout);


routes/index.jsを開いて、以下のように書き換えます。

routes/index.js

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

var loginCheck = function(req, res, next) {
    if(req.session.user){
        next();
    }else{
        res.redirect('login');
    }
};

router.get('/', loginCheck, function(req, res) {
    res.render('index', { user: req.session.user});
});

module.exports = router;

/にGETリクエストが来た場合、loginCheck関数で、session情報にemailが書き込まれているかをチェックします。

書き込まれていない場合は、/loginにリダイレクトし、書き込まれている場合は、index.htmlを表示します。


次に、routesディレクトリーに、追加したlogin.js、add.js、logout.jsを作成します。(ちなみに、モジュールをrequireで読み込む時は、"index"と".js"は省略できるらしいです。indexは消すとわかりにくいので書いたほうがベター)。

routes/login.js

var express = require('express');
var router = express.Router();
var model = require('../model');
var User = model.User;

router.get('/', function(req, res) {
    res.render('login');
});

router.post('/', function(req, res) {
    var email = req.body.email;
    var password = req.body.password;
    var query = {
        "email": email,
        "password": password
    };
    User.find(query, function(err, data) {
        if (err) {
            console.log(err);
        }
        if (data === "") {
            res.render('login');
        } else {
            req.session.user = email;
            res.redirect('/');
        }
    });
});

module.exports = router;

/loginに対してGETリクエストが来た場合は、login.htmlを表示します。

/loginにPOSTリクエストが来た場合は、入力されたemailとpasswordをデータベースと照合し、マッチした場合はemailをsession情報に書き込み、/にリダイレクトします。マッチするものがデータベースにない場合は、/loginを表示します。


add.js

var express = require('express');
var router = express.Router();
var model = require('../model.js');
var User = model.User;

router.post('/', function(req, res) {
    var newUser = new User(req.body);
    newUser.save(function(err) {
        if (err) {
            console.log(err);
            res.redirect('back');
        } else {
            res.redirect('/');
        }
    });
});

module.exports = router;

こちらは新規登録の処理です。/addにPOSTリクエストが来た場合に、データベースに新しいユーザーを登録します。


logout.js

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

router.get('/', function(req, res) {
    req.session.destroy();
    console.log('deleted session');
    res.redirect('/');
});

module.exports = router;

最後にログアウトの処理です。

/logoutにGETリクエストが来た場合、session情報を破棄して/にリダイレクトします。

 

Viewの作成

ここまでで、サーバー側の処理はひと通り書き終えました。

最後に、クライアント側に表示する部分を作ります。


今回は、HTMLテンプレートにejsを使うので、viewsディレクトリーにejsファイルを追加していきます。

views/login.ejs

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ログイン画面</title>
</head>

<body>
    <h2>ログイン</h2>
    <form action="/login" method="POST">
        <input type="text" name="email" placeholder="Email" />
        <input type="password" name="password" placeholder="Password" />
        <input type="submit">ログイン</button>
    </form>
    <h2>新規登録</h2>
    <form action="/add" method="POST">
        <input type="text" name="email" placeholder="Email" />
        <input type="password" name="password" placeholder="Password" />
        <input type="submit">新規登録</button>
    </form>
</body>

</html>

これはログイン画面です。

ログインフォームと、新規登録フォームがあります。

それぞれ、記入してsubmitすると、POSTリクエストをサーバーに送るようになっています。


views/index.ejs

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>サイト</title>
</head>

<body>
    <h1>My Site</h1>
    <p>Welcome to My Site</p>
    <%= user %> さんようこそ。
        <p>
            <a href="/logout">ログアウト</a>
        </p>
</body>

</html>

これはログイン後の画面になります。<%= user %>の部分には、routes/index.jsで指定した、emailが入ります。

また、ログアウト処理を行うためのリンクも書いておきます。

 

実行

では、アプリを実行してみましょう。

$ npm start

ブラウザから、127.0.0.1:3000にアクセスしてログイン画面が出れば成功です。

新規登録を行い、ログインすると、サイトの画面が表示されます。

ログアウトを押すと、ログイン画面に戻ります。

 

まとめ

私自身、このようなログイン機能を実装したのは初めてなので、他と比較はできないのですが、まあややこしいですね…。でも、Javasscriptで全部完結(HTML,CSSは必要)するところはやっぱり魅力的です。

 

次回は、セキュリティ対策のための「パスワードのハッシュ化」を行いたいと思います!平文で保存していた利用者のパスワードが、ハッキングによって流出…なんてことになってしまうと大変ですからね!

 

pangency.hatenablog.com

 

では、また。