Express と Passport と Express-Session と…

Express と Passport と Express-Session と…

今回は Express Generator で作成したアプリケーションにログイン機能を追加します。
ログイン機能にはPassportを,セッション機能にはExpress Sessionを使用します。
アプリケーションは前回までで作成したものを使用します。

環境:
・Windows10
・WSL2(Ubuntu20.04)
・Windows Terminal
・VSCode
・Express + ejs (Express Generator)
・passport
・express-session
・passport-local

目標:
・Express Generator で作成したアプリケーションにパスワード認証を追加
・パスワード認証に成功している場合のみsample画面に遷移

注意事項:
多々間違いがあると思いますが,ご了承ください。

PassportとExpress Sessionをインストール

passportとexpress-session,passport-localをインストールします。

okaki@DESKTOP:~/vscode/webappl/appl$ npm install passport express-session passport-local

ユーザ名とパスワードの入力欄作成

webappl/appl/views/index.ejsページにユーザ名とパスワードの入力欄を作成します。

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel="stylesheet" href="/stylesheets/style.css" />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>

    <!--   ここから追加   -->
    <form action="" method="POST" class="form">
      <p class="form__text">
        <input type="text" name="username">
      </p>
      <p class="form__text">
        <input type="text" name="password">
      </p>
      <input type="submit" value="ログイン">
    </form>
    <!--   ここまで追加   -->

  </body>
</html>

ユーザ名を入力するinputには”username”,パスワードを入力するinputには”password”と名前を付けます。

Passportの宣言

ログイン認証をおこなうための宣言を webappl/appl/routes/index.js に追加します。

//   webappl/appl/routes/index.js   //
var express = require("express");
var passport = require("passport");  // <-- 追加
var LocalStrategy = require("passport-local").Strategy;  // <-- 追加
var router = express.Router();

/* GET home page. */
router.get("/", function(req, res, next) {
  res.render("index", { title: "Express" });
});

module.exports = router;

Passportによる認証

Passportを使用するための定義を webappl/appl/routes/index.js におこないます。今回はユーザ名とパスワードによる認証をおこなうため,LocalStrategyを使用します。
サンプルプログラムのため,ユーザ名は「okaki」,パスワードは「password」に固定します。実際に使用する場合はファイルやDBから読むようにしたほうがいいですね。
passport.initialize() でPassportを初期化します。

//   webappl/appl/routes/index.js   //
var express = require("express");
var passport = require("passport"); 
var LocalStrategy = require("passport-local").Strategy;
var router = express.Router();

/*   ここから追加   */
passport.use("login_transition", new LocalStrategy(function(username, password, done){
  if(username==="okaki" && password==="password"){
    return done(null,username);
  }
  return done(null,false);
}));

router.use(passport.initialize());
/*   ここまで追加   */

/* GET home page. */
router.get("/", function(req, res, next) {
  res.render("index", { title: "Express" });
});

module.exports = router;

ログインボタンを押したときにログイン処理を行うためのpost処理を webappl/appl/routes/index.js に追加します。
ログイン認証が成功した場合はsample画面へ,失敗した場合はindex画面を再表示させます。
sample画面は以前作成したものです。

//   webappl/appl/routes/index.js   //
var express = require("express");
var passport = require("passport");  // <-- 追加 part3
var LocalStrategy = require("passport-local").Strategy;  // <-- 追加 part3
var router = express.Router();

passport.use("login_transition", new LocalStrategy(function(username, password, done){
  if(username==="okaki" && password==="password"){
    return done(null,username);
  }

  return done(null,false, {message: "ログイン失敗"});
}));

router.use(passport.initialize());

/* GET home page. */
router.get("/", function(req, res, next) {
  res.render("index", { title: "Express" });
});

/*   ここから追加   */
router.post("/",passport.authenticate(
  "login_transition",
  {
    successRedirect: "/sample",
    failureRedirect: "/",
    session: false
  }
));
/*   ここまで追加   */

module.exports = router;

これでパスワード認証が成功するとsample画面へ移行し,認証が失敗した場合はindex画面が再表示されます。

セッション処理の追加

今のままだと,ログインしていなくても localhost:3000/sample とブラウザに入力するとsample画面が表示されます。
直接Sample画面に遷移できないようにセッション処理を追加します。

まず,webappl/appl/app.js へセッション処理のために passport と express-session を宣言を追加します。

//   webappl/appl/app.js   //
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var passport = require("passport");  // <-- 追加
var session = require("express-session");  // <-- 追加

var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
var sampleRouter = require("./routes/sample");

var app = express();

/*~ 省略 ~*/

webappl/appl/app.js にセッション処理の設定を行います。

//   webappl/appl/app.js   //

/*~ 省略 ~*/

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(session({secret: "a1b2c3d4e6"}));  // <-- 追加
app.use(passport.session());  // <-- 追加

app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/sample", sampleRouter); 

/*~ 省略 ~*/

sessionのsecret属性はセッションIDをCookieに保存するときの署名に使用されます。任意の文字列を指定します。詳しくはこちら(外部リンク)。

ログインしているかどうかを判別するためにセッション情報からユーザ情報をシリアライズしたりデシリアライズする処理を追加します。

//   webappl/appl/app.js   //

/*~ 省略 ~*/

app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/sample", sampleRouter);

/*   ここから追加   */
passport.serializeUser(function(user, done) {
  done(null, user);
});

passport.deserializeUser(function(user, done) {
  done(null, user);
});
/*   ここまで追加   */

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

/*~ 省略 ~*/

ページ移行時に認証状況を確認

ページ移行時にログイン状況を確認します。
webappl/appl/routes/sample.js に処理を追加します。

//   webappl/appl/routes/sample.js   //
var express = require("express");
var router = express.Router();

/*   ここから削除   */
/* 
router.get("/", function(req, res, next) {
    res.render("sample", { title: "sample page" });
});
*/
/*   ここまで削除   */
/*   以下の処理に修正   */
router.get("/", function(req, res, next) {
    if(!req.isAuthenticated()){
        res.redirect("/");
    }else{
        res.render("sample", { title: "sample page" });
    }
});
/*   以上の処理に修正   */

module.exports = router;

req.isAuthenticated() の結果で遷移させる画面を分けています。

認証処理を修正

webappl/appl/routes/index.js の処理を修正してexpress-sessionを使用するようにします。

//   webappl/appl/routes/index.js   //

/*~ 省略 ~*/

router.post("/",passport.authenticate(
  "login_transition",
  {
    successRedirect: "/sample",
    failureRedirect: "/",
    session: true  // <- 修正
  }
));

module.exports = router;

以上でユーザ名とパスワードによる認証が出来ました。
ログインされている場合にのみsample画面に遷移します。ログインしていない場合は localhost:3000/sample とブラウザに入力してもトップページに遷移すれば成功です。

ログアウト機能

ログアウト機能を作成します。
webappl/appl/routes/index.js にログアウト処理を追加します。

//   webappl/appl/routes/index.js   //

/*~ 省略 ~*/

router.post("/",passport.authenticate(
  "login_transition",
  {
    successRedirect: "/sample",
    failureRedirect: "/",
    session: true  // <-- 修正 part4
  }
));

/*   ここから追加   */
router.get('/logout', function(req, res, next) {
  req.logout(function(err) {
    if (err) {
      return next(err);
    }
    res.redirect('/');
  });
});
/*   ここまで追加   */

module.exports = router;

これで /localhost:3000/logout にアクセスするとログアウトが実行され,トップページに遷移します。
ログイン後にログアウトを実行して,sample画面に直接遷移できなければ成功です。

最後に

今回はここまで。

これでログインとログアウトができるようになりました。