海未「では、前回までに作ったアプリケーションに、ユーザ認証の機能を追加してみます」
穂乃果「いよいよ本格的になってくるねっ!」
海未「仕様は」
- グループ登録画面に入るために認証が必要
- ログインされていればそのままグループ登録画面を表示
- ログインされていなければログイン画面を表示
- ユーザ登録はログイン画面から行える
海未「こんなところでいいでしょう。では、必要なモジュールを追加します」
$ npm install express-session --save
$ npm install connect-mongo --save
海未「セッション管理を行うモジュールexpress-session
と、セッション情報をMongoDBに保存するためのconnect-mongo
を使います」
海未「セッション管理の基本設定をしましょう。app.js
に書き足します」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
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 session = require('express-session'); var MongoStore = require('connect-mongo')(session); var routes = require('./routes/index'); var add = require('./routes/add'); 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(path.join(__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: 'lovearrowshoot', saveUninitialized: false, resave: false, store: new MongoStore({ url: 'mongodb://localhost/polls', clear_interval: 60 * 60 }), cookie: { httpOnly: true, maxAge: 60 * 60 * 1000 } })); // snip |
海未「8~9行目ではモジュールの読み込みを行います。connect-mongo
については、セッションを渡すとそれをMongoDBで管理するためのオブジェクトを返します」
海未「28行目以降が、セッション管理機能をアプリに組み込む部分です」
ことり「いろんな設定が、あるみたいだけど・・・」
海未「secret
は、cookieの暗号化に使われる文字列です。誰かに見られるものではありませんから、何を指定しても構いません」
ことり「ふーん、やっぱり海未ちゃん、誰にも見られないところでは『あなたのハート撃ち抜くぞ♡ばーん』とかやってるんだ♪」
海未「・・・っ!」
穂乃果「海未ちゃんの自爆芸も板についてきたね・・・」
海未「saveUninitialized
とresave
は、一般的にはfalse
でいいでしょう。セッションの保存タイミングを制御するものです」
海未「store
がMongoDBの設定です。URLはこれまで通り、clear_interval
は、セッションの有効期間です。ここでは1時間にしていますが、運用に合わせて適宜変更するといいでしょう」
海未「cookie
はcookie関係の設定ですが、httpOnly
をtrue
にすることで、HTTP接続の場合のみcookieにアクセスできるようにしています。maxAge
はcookieの有効期間で、これも1時間にしています」
穂乃果「この有効期間、単位どうなってるの?」
海未「clear_interval
は秒、maxAge
はミリ秒です。似たような設定でも単位が違うのでややこしいですね」
海未「これで設定は完了です。では、ログイン画面を作りましょう。views/login.jade
にフォームを2つ実装します」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
extends layout block content h1 Login p.caution= error form(method='POST', action='/login') input(type='text', name='name', placeholder='user name') input(type='password', name='password', placeholder='password') input(type='submit', value='ログイン') form(method='POST' action='/adduser') input(type='text', name='name', placeholder='user name') input(type='password', name='password', placeholder='password') input(type='submit', value='新規登録') |
ことり「この画面でログインと登録両方できるんだ」
海未「分けてもいいのですが、特に複雑なわけでもないので1画面で十分かと」
海未「今までのグループに加えて、ユーザという新しいデータを扱うことになりました。モデルを追加しましょう」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var mongoose = require('mongoose'); var url = 'mongodb://localhost/polls'; var db = mongoose.createConnection(url, function(err, res) { if (err) { console.log(`error: ${err}`); } else { console.log(`success connected ${url}`); } }); var GroupSchema = new mongoose.Schema({ name: String, votes: Number }); exports.Group = db.model('Group', GroupSchema); var UserSchema = new mongoose.Schema({ name: String, password: String }); exports.User = db.model('User', UserSchema); |
海未「UserSchema
を追加しました。パスワードは平文保存ですが、今はいいでしょう」
ことり「本番では、ダメ、絶対」
海未「では、/adduser
と/login
のコントローラを実装していきます。まずはroutes/adduser.js
から」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var express = require('express'); var router = express.Router(); var User = require('../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; |
海未「やっていることはグループの登録と同じですから、説明は割愛しますね」
海未「次に、ログイン処理をroutes/login.js
に実装します」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var express = require('express'); var router = express.Router(); var User = require('../model').User; router.get('/', function(req, res) { res.render('login'); }); router.post('/', function(req, res) { var name = req.body.name; var password = req.body.password; var query = { "name": name, "password": password }; User.find(query, function(err, data) { if (err) { console.log(err); } if (data === "") { res.render('login'); } else { req.session.user = name; res.redirect('/add'); } }); }); module.exports = router; |
海未「GETの処理はログイン画面を表示するだけですからいいでしょう。問題はPOSTの方です」
穂乃果「最初はわかるよ。リクエストパラメータからユーザ名とパスワードを取り出して、えーとそれから・・・」
海未「そこから先はDBへのアクセスです。合致するデータがDBにあるかを検索しています」
ことり「User.find
かな?」
海未「そうです。find
はroutes/index.js
にも出てきましたが、ここでは第1引数は検索条件、第2引数にコールバック関数を取ります」
ことり「え~っと、検索結果が空だったらログイン画面、ヒットしたらグループ登録画面かな」
海未「はい。その際に、セッション情報にユーザ名を書き込んでいます」
海未「では、グループ登録画面にログイン判定を実装しましょう。routes/add.js
を」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var express = require('express'); var router = express.Router(); var Group = require('../model.js').Group; var sessionCheck = function(req, res, next) { if (req.session.user) { next(); } else { res.redirect('login'); } }; router.get('/', sessionCheck, function(req, res, next) { res.render('add', {user: req.session.user}); }); router.post('/', function(req, res, next) { var data = req.body; data['votes'] = 0; var newGroup = new Group(data); newGroup.save(function(err) { if (err) { console.log(err); res.redirect('back'); } else { res.redirect('/'); } }); }); module.exports = router; |
海未「sessionCheck
関数で、セッション情報にユーザ名があるかないかをチェックして、なければログイン画面を表示しています」
穂乃果「そのsessionCheck
は、ええと・・・get
で呼んでるのかな」
海未「get
やpost
は第2引数に関数を渡すと、前処理を行うことができます。それを利用して、リクエストの処理前にチェック処理を入れています」
海未「最後に、グループ登録画面にユーザ名を表示してみましょう。さきほどのadd.js
でuser
パラメータを追加しましたから」
1 2 3 4 5 6 7 8 9 |
extends layout block content h1 グループ登録 p ようこそ#{user}さん form(method='POST', action='/add') input(type='text', name='name', placeholder='グループ名') input(type='submit', value='登録') |
海未「こんな感じでしょう」
穂乃果「これでできあがり?」
海未「はい。実際に動かしてみてください」
穂乃果「えーと、グループ登録画面に行こうすとるとログイン画面が表示されて・・・」
ことり「登録してからログインすると、グループ登録画面に行けて、名前が表示されてる!」
海未「セッション管理というと難しそうなイメージがありますが、基本的なところはそう難しくなく実装できますね」
穂乃果「さて海未ちゃん、これ何回目の中締め?」
海未「JavaScript関係はネタが尽きませんから・・・そのうちAngularJSとかTypeScriptとかやりはじめるかもしれません」
ことり「じゃあまたきっと、この3人で集まれるよね♪」
穂乃果「もっちろん! それじゃ、今回もお相手は高坂穂乃果とっ!」
ことり「南ことりと♪」
海未「園田海未でした♡」
3人「「「まったねーっ!!」」」