ExpressでJWT認証を実装しようとしたが、DB接続まで行っている記事が見当たらなかったので勉強も兼ねて作成しました。
あと、筆者がPHP(Laravel)に慣れているためrouterとcontrollerを分けて書いています
参考記事
準備
環境構築
省略
使用ライブラリ
- 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
'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
'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
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
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の導入など色々やってみたいと思います。
一旦ここまでできたので、余力があればトークンチェックなどログイン後の処理編も書きたいと思っています。
正直、端折りすぎたのでコメントなどいただければ修正していきたいと思います。