nodejs + express + mysql + passport を用いて簡単なログイン機能

# はじめに
ログイン機能はとても基本的な内容であるが、ある意味ともて大事な機能である。
最近は自分のデータベースにパスワードを保存せず、GoogleやFacebookのIDを代わりに利用できるので、
ユーザーの個人情報管理の重要さは、個人が運営するサイトなどにおいては減っている気がする。
ここではnodejs + express + mysqlを用いてログイン機能、方法を書く。

# 環境
window 10
mysql 8.0.31
express: 4.18.2

# Code
## Mysql
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/ef6188a8-be46-0649-cce7-34da3d26adbc.png)
DBeaverからmysqlのaccountsのテーブルにこのようにユーザーの個人情報と、id, passwordを入れた。
ここでuser_idは固有の識別番号として設定した。

## Nodejs
```:passport install
 npm install passport passport-local express-session
``` 
```javascript:server.js
const express = require('express');
const mysql = require('mysql');
const app = express();
const bodyParser = require('body-parser');

require('dotenv').config()
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine', 'ejs')
app.use('/public', express.static('public'))



// mysqlに接続
const con = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: process.env.MYSQL_PASSWORD,
    database: 'nodejs',
    dateStrings: "date",
    multipleStatements: true
});

// 他のファイルでmysqlを使えるようにexportします
module.exports = con

con.connect((err) => {
    if (err) {
        console.log('error connecting: ' + err.stack);
        return;
    }
});

// ここでlogin関連のapiを管理
app.use('/', require('./routes/login.js'));

app.listen(8080, function () {
    console.log('listening on 8080')
});

```

```javascript:login.js
var router = require('express').Router();

// mysql
const con = require('./../server.js')

// loginを管理できるライブラリー
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');

router.use(session({ secret: '1234', resave: true, saveUninitialized: false }));
router.use(passport.initialize());
router.use(passport.session());

// loginページ
router.get('/login', function (req, res) {
    res.render('login.ejs')
});

// login
// loginに失敗した場合、/failに飛ばす
router.post('/login', passport.authenticate('local', { failureRedirect: '/fail' }), function (req, res) {
    res.redirect('/')
});

passport.use(new LocalStrategy({
    // id, pw のそれぞれのinputのnameと合わせる
    usernameField: 'id',
    passwordField: 'pw',
    session: true,
    passReqToCallback: false,
}, function (input_id, input_pw, done) {
    const sql = `select * from accounts where id = '${input_id}'`
    con.query(sql, function (err, result) {

        // mysql から取り出したaccounts情報から１番目の情報と照合する
        // 実際はもっとちゃんとした方がいいですが、なるべくシンプルに
        login_data = result[0]
        if (err) return done(err)
        if (!login_data) return done(null, false, { message: 'account does not exist' })
        if (input_pw == login_data.pw) {
            return done(null, login_data)
        } else {
            return done(null, false, { message: 'wrong password' })
        }
    })
}));

// ログインに成功したらuser.idのセッションを生成し、Cookieを作る
passport.serializeUser(function (user, done) {
    done(null, user.id)
});

passport.deserializeUser(function (user_id_saved, done) {
    // ユーザーの情報をDBから探す
    const sql = `select * from accounts where id = '${user_id_saved}'`
    con.query(sql, function (err, result) {
        done(null, result[0])
    })
});


// ミドルウェアでログイン有無によるページ接近の管理
function is_login(req, res, next) {
    if (req.user) {
        // loginした状態なら、通す
        next()
    } else {
        // loginしてないならこのページに飛ばす
        res.render('login.ejs')
    }
}

// ミドルウェアでログインしているかチェックし、ログインしているならreq.userのDBを見せる
router.get('/mypage', is_login, function (req, res) {
    // deserializeUserで得られたuserのDBデータ持ってくる
    console.log(req.user)
    res.send(req.user)
})



module.exports = router;
```
## ejs(html)
```html:login.ejs
<!DOCTYPE html>
<html lang="en">
  <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="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
    integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N"
    crossorigin="anonymous"
  /> 
    <title>Document</title>
  </head>
  <body>
    <div class="container mt-4">
      <form action="/login" method="POST">
        <div class="form-group">
          <label>id</label>
          <input type="text" class="form-control" name="id" />
        </div>
        <div class="form-group">
          <label>pw</label>
          <input type="text" class="form-control" name="pw" />
        </div>
        <button type="submit" class="btn btn-danger">login</button>
      </form>
    </div>
  </body>
</html>
```
最低限の`input`と`button`だけ備えた。

# ログイン
## ログイン失敗
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/e676b120-511c-0f76-82d6-d72f0427ca35.png)
ログインに失敗したとき、先ほど設定した`/fail`に飛ばされる。 
このページは別で設定した404ページ

## ログイン成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/ba3891fa-56ef-c81f-0586-f3cc42ebbe0b.png)

## Cookie
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/84ea11e3-6522-ba45-dd5a-2f87b853ad9b.png)


ログインした後、Cookieが生成されたことがわかる。これによってログインしている状態であることが証明され、サーバーは現在のuser情報の一部をCookieから得ることができる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/a0ac2dc6-b88a-87d0-2fa4-383c2c30da95.png)



ログイン後、DBからユーザー情報を取り出した。
これによってマイページで自分の情報を見せ、個人情報を変更したり、パスワードを変更したりすることができる。

# おわりに
コードが長くて難しいように見えるが、データベースから入力したid と一致するデータを取り出し、pwも一致ならログイン、それ以外なら失敗するだけのコードです。そしてそのidの情報をCookieに保存し、必要なときはDBからその都度、取り出して用いる。
あとの機能はすべてライブラリーの力にお任せです。
今回はmysqlで紹介しましたが、mongoDBのように、他のDBでも原理は一緒なのでpassportでログイン機能を作ることができます。