express・sequelize でJWT認証を実装した

ExpressでJWT認証を実装しようとしたが、DB接続まで行っている記事が見当たらなかったので勉強も兼ねて作成しました。

あと、筆者がPHP(Laravel)に慣れているためrouterとcontrollerを分けて書いています
## 参考記事
https://qiita.com/zaburo/items/985db4ccbb0cdb5c9095

https://qiita.com/sukeo-sukeo/items/6e86906d88e1110bbb36

https://qiita.com/a1k4r/items/82c62b7783837090ef27

https://blog.katsubemakito.net/nodejs/mysql-sequelize2
## 準備

### 環境構築

省略

### 使用ライブラリ

- express
- sequelize
- dotenv(hash時のsaltとToken secretを.envに記述するため)
- bcrypt-nodejs
- jsonwebtoken

## 実装

### モデル・マイグレーション

今回はsequelizeを使用してモデルとマイグレーションを作成しました

生成
`yarn sequelize-cli model:generate --name Users --attributes name:string,email:string,password:string,rememberToken:string`

マイグレーション実行
`yarn sequelize-cli db:migrate`

### Migration
./migrations/******-create-user.js
```javascript
'use strict';
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      email: {
        type: Sequelize.STRING
      },
      password: {
        type: Sequelize.STRING
      },
      rememberToken: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users');
  }
};
```

### Model
./models/user.js
```javascript
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  User.init({
    name: DataTypes.STRING,
    email: DataTypes.STRING,
    password: DataTypes.STRING,
    rememberToken: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};
```
### Router

./routes/auth.js
```javascript
const express = require('express');
const router = express.Router();
const authController = require('../controller/authController');

// 会員登録
router.post('/register',(req,res,next) => authController.register(req,res,next))
// ログイン
router.post('/login',(req,res,next) => authController.login(req,res,next))
module.exports = router;
```

### Controller

./controllers/authController.js

```javascript
const db = require("../models/index");
const bcrypt = require("bcrypt-nodejs");
const jwt = require("jsonwebtoken");

// userモデル
const user = db.User;
// bcrypt.genSaltで生成した値を.envから取得
const salt = process.env.HASH_SALT;

const authController = {
    login: async (req, res, next) => {
        try{
            if(!req.body.email || !req.body.password){
                // メール、パスワードがそろってない
                return res.status(401).json({message: "Unauthorized"});
            }
            // 平文パスワードをハッシュ化
            const hashedPassword = bcrypt.hashSync(req.body.password, salt);
            const userData = await user.findOne({
                where:{
                    email: req.body.email,
                    password: hashedPassword
                }
            });
            if(!userData){
                // ログイン失敗
                return res.status(401).json({message: "Unauthorized"});
            }
            const tokenData = {
                name: userData.dataValues.name,
                email:userData.dataValues.email,
            }
            // jwtトークンを発行
            const token = jwt.sign(tokenData , process.env.JWT_SECRET, { expiresIn: "1h" });
            res.json({message: "login finished", token});
        } catch (e){
            console.error(e);
            return res.status(500).json({message: "failed"});
        }
    },
    register: async (req, res, next) => {
        try {
            if(!req.body.name || !req.body.email || !req.body.password){
                // ログインデータがそろってない
                return res.status(404).json({message: "invalid data"});
            }
            // パスワードハッシュ化
            const hashedPassword = bcrypt.hashSync(req.body.password, salt);
            // ユーザー作成
            user.create({
                name: req.body.name,
                email: req.body.email,
                password:hashedPassword
            })
            res.json({message:"created user"})
        } catch (e){
            console.error(e);
            return res.status(500).json({message: "failed"});
        }

    }
}

module.exports = authController;
```

## 結論
と、ザッとこんな感じです。
今回始めてexpressに触ったので手探りでしたが、思ったよりサクッと実装できました。
社内ではAPIはLaravelメインで実装していますが、この感じならexpressでAPIを書くのもアリかと。

フロントはReact(nextjs)なので、同じ言語で書けるメリットは大きいですね。
今後TSの導入など色々やってみたいと思います。

一旦ここまでできたので、余力があればトークンチェックなどログイン後の処理編も書きたいと思っています。

正直、端折りすぎたのでコメントなどいただければ修正していきたいと思います。