今回利用する主なモジュールのバージョンは以下の通りです。
node v0.12.2
express v4.12.1
mongod v2.4.9
passport v0.2.1
passport-local v.1.0.0
passport-local-mongoose v.1.0.0
今回のサンプルアプリではデータベースに MongoDB を利用します。MongoDB のインストールが未だでしたらこちらを参考にMongoDBをインストールしておいてください。
Expressプロジェクトの雛形を作成
Express4なプロジェクトの雛形を作るためにまずは express-generator をインストールして
$ sudo npm install -g express-generator
雛形生成を実行します。プロジェクト名は passport-express4-example としました。
$ express passport-express4-example
生成されたExpress4プロジェクト構成はこのようになります。
$ tree passport-express4-example/
passport-express4-example/
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.jade
├── index.jade
└── layout.jade
依存関係を追加
package.json に dependencies (依存ライブラリ)を追加しましょう。太字は追加した箇所です。
{ "name": "passport-express4-example", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.12.0",。。。。。 "cookie-parser": "~1.3.4", "debug": "~2.1.1", "express": "~4.12.2", "jade": "~1.9.2", "morgan": "~1.5.1", "serve-favicon": "~2.2.0", "express-session": "^1.10.1", "mongoose": "^4.0.2", "passport": "^0.2.1", "passport-local": "^1.0.0", "passport-local-mongoose": "^1.0.0", "should": "~2.1.0", "chai": "~1.8.1", "mocha": "~1.14.0" } }
npm install で依存モジュールをインストールします。
$ cd passport-express4-example $ sudo npm install
さて、ここで一旦サンプルアプリが動くことを確認しておきましょう。
$ npm start
http://localhost:3000/ にアクセスしてWelcomeページが表示されているか確認してください。
ちゃんと動いていることが確認できたら Ctrl+C でサンプルアプリを強制終了して次に進みます。
MongoDBの起動
次に MongoDB を起動します。
ここでは datapath と logpath を指定していますが、ここは皆さんの環境に合わせて書き換えてください。
$ sudo mongod --dbpath ~/tmp/mongodb/data/ --logpath ~/tmp/mongodb/mongodb.log &
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 mongoose = require('mongoose'); var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var routes = require('./routes/index'); 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(require('express-session')({ secret: 'secret secret', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', routes); // passport config var Account = require('./models/account'); passport.use(new LocalStrategy(Account.authenticate())); passport.serializeUser(Account.serializeUser()); passport.deserializeUser(Account.deserializeUser()); // mongoose mongoose.connect('mongodb://localhost/passport_local_mongoose_express4'); // 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;
modelsを作成
次にプロジェクトルート直下に models フォルダを作り、account.js を新規作成します。これは MongoDB に格納されるユーザアカウント情報のモデルです。
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var passportLocalMongoose = require('passport-local-mongoose'); var Account = new Schema({ username: String, password: String }); Account.plugin(passportLocalMongoose); module.exports = mongoose.model('Account', Account);
routesを作成
次にプロジェクトルート直下の routes フォルダの index.js を以下のように編集します。今回はルーティング情報を index.js に集約させることにしましょう。
var express = require('express'); var passport = require('passport'); var Account = require('../models/account'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { console.log("req.user", req.user); res.render('index', { user: req.user }); }); /* GET register page */ router.get('/register', function(req, res) { res.render('register', {}); }); /* Register a user account */ router.post('/register', function(req, res) { Account.register(new Account({ username: req.body.username }), req.body.password, function(err, account) { console.log("req.body.username", req.body.username); if (err) { console.log("err", err); return res.render("register", { errInfo: "Sorry. That username already exists. Try again." }); } passport.authenticate('local')(req, res, function() { res.redirect('/'); }); }); }); /* GET login page */ router.get('/login', function(req, res) { res.render('login', { user: req.user, errInfo: req.query.err }); }); /* Try to login */ router.post('/login', passport.authenticate('local', { failureRedirect: "/login?err=unauthorized" }), function(req, res) { res.redirect('/'); }); /* Do logout */ router.get('/logout', function(req, res) { req.logout(); res.redirect('/'); }); /* GET member-only page */ router.get('/member', function(req, res) { if (!req.isAuthenticated()) res.redirect("/login"); res.render('member', { user: req.user }); }); /* For checking if webserver will be dead or alive */ router.get('/ping', function(req, res) { res.status(200).send("pong!"); }); module.exports = router;
ここまできたらもう一度アプリを立ち上げてエラーが無いか確認しておきましょう。
$ npm start
問題なくアプリが立ち上がったら http://localhost:3000/ping を叩いてみてください。/ping はアプリの生存確認のために作ったAPIです。画面に pong! が返ってくればアプリは正常に動いています。
viewsを作成
次に画面を編集 or 新規作成します。画面を定義する jade ファイルが views フォルダ配下にあります。
layout.jade (全画面共通レイアウト)
doctype html html head title= title meta(name='viewport', content='width=device-width, initial-scale=1.0') link(href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', rel='stylesheet', media='screen') link(rel='stylesheet', href='/stylesheets/style.css') body block content script(src='http://code.jquery.com/jquery.js') script(src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js')
index.jade (トップページ)
extends layout block content if (!user) a(href="/login") Login br a(href="/register") Register if (user) p You are currently logged in as #{user.username} a(href="/logout") Logout
login.jade (ログインページ)
extends layout block content .container h1 Login Page p.lead Say something worthwhile here. br form(role='form', action="/login",method="post", style='max-width: 300px;') .form-group input.form-control(type='text', name="username", placeholder='Enter Username') .form-group input.form-control(type='password', name="password", placeholder='Password') button.btn.btn-default(type='submit') Submit a(href='/') button.btn.btn-primary(type="button") Cancel
register.jade (会員登録ページ)
extends layout block content .container h1 Register Page p.lead Say something worthwhile here. br form(role='form', action="/register",method="post", style='max-width: 300px;') .form-group input.form-control(type='text', name="username", placeholder='Enter Username') .form-group input.form-control(type='password', name="password", placeholder='Password') button.btn.btn-default(type='submit') Submit a(href='/') button.btn.btn-primary(type="button") Cancel br h4(style="color:red")= errInfo
member.jade (会員専用ページ)
extends layout block content if(user) p Hi #{user.username}. This page is member-only. a(href="/logout") Logout
express4 + passport な会員サイトのサンプルアプリは以上で出来上がりです。
出来上がったアプリの機能一覧は以下のとおりとなります。
トップページ
http://localhost:3000/
会員登録ページ
http://localhost:3000/register
ログインページ
http://localhost:3000/login
会員専用ページ
http://localhost:3000/member
ログアウト
http://localhost:3000/logout
Unit Test
アプリができたら念の為Unitテストもしておきましょう。
初めに package.json の dependencies に追加した mocha, chai, should はUnitテストのためのモジュールです。
プロジェクトルートに test フォルダを新規作成し、 test.user.js を新規作成します。
var should = require("should"); var mongoose = require('mongoose'); var Account = require("../models/account.js"); var db; describe('Account', function() { before(function(done) { db = mongoose.connect('mongodb://localhost/test'); done(); }); after(function(done) { mongoose.connection.close(); done(); }); beforeEach(function(done) { var account = new Account({ username: '12345', password: 'testy' }); account.save(function(error) { if (error) console.log('error' + error.message); else console.log('no error'); done(); }); }); it('find a user by username', function(done) { Account.findOne({ username: '12345' }, function(err, account) { account.username.should.eql('12345'); console.log(" username: ", account.username); done(); }); }); afterEach(function(done) { Account.remove({}, function() { done(); }); }); });
上記の test.user.js ではユーザアカウントの新規登録・取得・削除までをひと通りテストします。
以下のコマンドでUnitテストを実行することができます。
$ node_modules/mocha/bin/mocha test/ test.user.js
child_process: customFds option is deprecated, use stdio instead.
no error
username: 12345
․
1 passing (222ms)
エラーは検出されませんでしたので、これにてUnitテストも無事終了です。
■参考にさせていただいたサイト
User Authentication with Passport and Express 4 - Michael Herman
■関連記事
Express4+PassportでGoogle OAuth認証の簡単なサンプル
0 件のコメント:
コメントを投稿