passport を使ったアプリケーション

使ったモジュール

  • 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 express-generator

ディレクトリ構成

  • 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
    • app.js
    • db.js
    • package-lock.json
    • package.json

データベース

データベース データベース名 テーブル名
mariadb mysite users

データベースの作成

CREATE DATABASE mysite;

SQL - テーブルの作成

CREATE TABLE users (
  id INT AUTO_INCREMENT,
  mail VARCHAR(255) NOT NULL,
  password VARCHAR(255) NULL,
  PRIMARY KEY (id)
);

サンプルデータの投入

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>