準備
利用するパッケージには以下のものがあります。
- cookie-parser@1.4.6
- debug@2.6.9
- ejs@3.1.9
- express-session@1.18.0
- express-validator@7.0.1
- express@4.18.3
- http-errors@1.6.3
- morgan@1.9.1
- mysql2@3.9.2
- passport-local@1.0.0
- passport@0.7.0
「 Express Generator 」で作成し、追加でインストールしたパッケージは「 passport, passport-local, express-session, mysql2, express-validator, connect-flash 」です。
パッケージをインストールするには、以下のコマンドを実行します。
npm install passport passport-local express-session express-validator connect-flash mysql2
ディレクトリ構成は、以下のようにします。
- example
- bin
- www
- middleware
- middleware.js
- node_modules
- ...
- public
- images
- javascripts
- stylesheets
- style.css
- routes
- content.js
- index.js
- user.js
- views
- content
- main.ejs
- user
- login.ejs
- error.ejs
- index.ejs
- content
- app.js
- db.js
- package-lock.json
- package.json
- bin
データベース
データベースは、MariaDB を利用します。
CREATE DATABASE mysite;
CREATE TABLE users (
id INT AUTO_INCREMENT,
mail VARCHAR(255) NOT NULL,
password VARCHAR(255) NULL,
PRIMARY KEY (id)
);
以下の SQL を実行しデータを投入します。実際に使うアプリでは、パスワードは暗号化しておく必要があります。
INSERT INTO users (mail, password) VALUES ('test', 'password');
app.js
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const passport = require('passport'); // 追加
const LocalStrategy = require('passport-local').Strategy; // 追加
const session = require('express-session'); // 追加
const mysql = require('mysql2/promise'); // 追加
const flash = require('connect-flash'); // 追加
const db_conf = require('./db'); // 追加
const pool = mysql.createPool(db_conf); // 追加
var indexRouter = require('./routes/index');
var userRouter = require('./routes/user'); // 追加
var contentRouter = require('./routes/content'); // 追加
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(session({
secret: 'secret_key',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize()); // 追加
app.use(passport.session()); // 追加
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(flash()); // 追加
// 追加
passport.serializeUser(function(user, done) {
done(null, user);
});
// 追加
passport.deserializeUser(function(user, done) {
done(null, user);
});
// 追加
passport.use(new LocalStrategy({
usernameField:'email',
passwordField:'password',
passReqToCallback: true
},
( req, email, password, done) => {
(async () => {
try {
const [results, fields] = await pool.query('SELECT * FROM users');
console.log(results);
console.log(results[0].mail);
console.log(results[0].password);
console.log(password);
if(email !== results[0].mail && password !== results[0].password){
return done(null, false, { message: 'メールアドレスとパスワードが違っています' });
} else if(email !== results[0].mail){
return done(null, false, { message: 'メールアドレスが違っています' });
} else if(password !== results[0].password){
return done(null, false, { message: 'パスワードが違っています' });
} else {
console.log("mail" + email)
return done(null, email);
}
} catch (err) {
console.log(err);
}
pool.end();
})();
}
));
app.use('/', indexRouter);
app.use('/user', userRouter); // 追加
app.use('/content', contentRouter); // 追加
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
LocalStrategy 内の認証が失敗した時の処理の done() の第3引数に { message : String } を渡すと、flash メッセージが蓄積され、「 req.flash('error') 」で呼び出せます。
データベースの設定
db.js
// db.js
const conf = {
host:"localhost",
user:"root",
password:"password",
database:"mysite"
}
module.exports = conf;
ルーティングの設定
routes/content.js
// routes/content.js
var express = require('express');
var router = express.Router();
const md = require('./../middleware/middleware');
router.get('/', md.authCheck, (req, res) => {
console.log(req.user)
res.render('content/main', { email: req.user });
});
module.exports = router;
routes/user.js
// routes/user.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const mysql = require('mysql2/promise');
const { check, validationResult } = require('express-validator');
const db_conf = require('../db');
const pool = mysql.createPool(db_conf);
router.get('/login', (req, res) => {
res.render('user/login', { error: req.flash( 'error' )});
});
const validator = [
check('email', 'メールアドレスが未入力です。').not().isEmpty(),
check('password', 'パスワードが未入力です。').not().isEmpty(),
];
router.get('/login', (req, res) => {
res.render('user/login', { error: req.flash( 'error' )});
})
router.post('/login', validator, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
req.flash('error', '');
res.render("user/login", { errors : errors.array(), error: req.flash( 'error' ) });
return;
}
next();
},
passport.authenticate('local',
{
successRedirect: '/content', //ログイン成功時に遷移したい画面
failureRedirect: '/user/login', //ログイン失敗時に遷移したい画面
session: true,
failureFlash: true
}
));
router.post('/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/user/login');
});
});
module.exports = router;
認証チェックの関数をミドルウェアにする
middleware/middleware.js
// middleware/middleware.js
module.exports = {
authCheck: function(req, res, next){
if(req.isAuthenticated()){
console.log("成功");
return next();
}else{
console.log("失敗");
res.redirect("/user/login");
next();
}
}
}
ビュー
views/content/main.ejs
!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel='stylesheet' href='/stylesheets/style.css' />
<title>content main</title>
</head>
<body>
<h1>content main</h1>
<p>ようこそ:<%= email %></p>
<p>
<form name=f method=POST action="/user/logout"></form>
<a href="javascript:document.f.submit()">リンクから submit</a>
</p>
<div>
<form class="logout" name="logout" action="/user/logout" method="post">
<input type="submit" value="Logout">
</form>
</div>
</body>
</html>
views/user/login.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ログインフォーム</title>
</head>
<body>
<h1>ログインフォーム</h1>
<form action="/user/login" method="POST">
<div id="error">
<!-- flash用のエラー -->
<% if(error != '') { %>
<% for(let e of error){ %>
<%= e %>
<% } %>
<% } %>
<!-- express-validator 用のエラー -->
<% if(typeof errors !== 'undefined') { %>
<% for(let e of errors){ %>
<p><%= e.path %>:<%= e.msg %></p>
<% } %>
<% } %>
</div>
<p><label for="email">email:<input type="text" name="email" id="email"></label></p>
<p><label for="password">password:<input type="password" name="password" id="password"></label></p>
<input type="submit"></button>
</form>
</body>
</html>