#はじめに
メインはフロントですが、趣味でExpressを触っているものです。サインアップ機能を実装するにあたってexpress-validatorを採用したので実装に至るまでのコードをメモします。後半にセキュリティ面も考慮したログイン画面も解説しているので最後までご覧いただけますと幸いです。
#express-validatorを使ってミドルウェアを作る
validateMiddleware.jsにバリデートをかけているコードをまとめています。単純にemailに対してメールアドレスとしての形式をとっているかのチェックととpasswerdに対して最低6文字以上のバリデートをかけています。たったこれだけの記述でバリデーションを実装できるので便利ですね。
const { check, validationResult } = require("express-validator");
const validateParam = (req, res, next) => {
return [check("email").isEmail(), check("password").isLength({ min: 6 })];
};
module.exports = { validateParam };
const express = require("express");
const router = express.Router();
const authController = require("../controllers/authController.js");
const { validateParam } = require("../middleware/validateMiddleware.js");
router.post("/signup", validateParam(), authController.signup_post);
module.exports = router;
以下MVCっぽくまとめています。Firebase Admin SDKとJWTの実装の解説についてはまた今度まとめます。ひとまずコードを分けたときにどのような実装になるのかメモです。
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const { check, validationResult } = require("express-validator");
const admin = require("../plugins/firebase");
const db = admin.firestore();
const signup_post = async (req, res) => {
const { email, password } = req.body;
//validation here
const errors = validationResult(req);
if (!errors.isEmpty()) {
// failed!
const newErrors = {};
errors.errors.forEach((err, index) => {
if (err.param.includes("email")) {
newErrors.email = "Please enter a valid email";
} else if (err.param.includes("password")) {
newErrors.password = "Minimum password length is a 6 characters";
}
});
return res.status(422).json(newErrors);
}
//bcript here
const salt = await bcrypt.genSalt();
const hashPassword = await bcrypt.hash(password, salt);
//firestore here
await db
.collection("users")
.add({
email,
hashPassword,
})
.then(async (result) => {
const token = await createToken(result.id);
res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1800 });
res.status(201).json({ user: result.id });
});
};
#セキュリティ面を考慮してログイン画面を実装する
ログイン画面の実装においてバリデートエラーはどのような文言を採用していますでしょうか?特にログインIDにサインアップでユーザーが登録したEmailを採用していることもあるかと思います。この場合のバリデートエラーは下記になるかと思います。
ここで問題になるのが、バリデートエラーがの文言が「inncorect email!」になっているという点です。これはつまり「inncorect email!」が表示されていなければ、この世に存在するEmailであると第3者に知られてしまいます。これはセキュリティの観点から堅牢とは言い難い実装になってしまいますね。(ついでにラベルも「Email」になっているのも第3者にログインIDにEmailを使っていることが知られてしまいますね。)
最低限ラベルとバリデートエラーは「Login ID」という文言に変え第3者に予測されにくい構造にすべきです。それ以外にはログイン失敗に回数制限をつけることや、そもそもログインIDをランダムで生成されるものにするなど方法は多岐に渡ると思うので一度ググってみてください。
「ログインID」のみの文言では登録してくださったユーザーにとってログイン時に「あれ?ログインIDってなんだっけ?」と多少不便になるかもしれませんが開発者として気を付けるべき箇所だと思います。
###実装したコード(ログイン機能)
const login_post = async (req, res) => {
const { email, password } = req.body;
await db
.collection("users")
.where("email", "==", email)
.get()
.then((docSnapshot) => {
const user = [];
docSnapshot.forEach((doc) => {
user.push({ id: doc.id, ...doc.data() });
});
return user[0];
})
.then(async (user) => {
console.log(user);
const newErrors = {};
if (user) {
const auth = await bcrypt.compare(password, user.hashPassword);
if (auth) {
const token = await createToken(user.id);
res.cookie("jwt", token, { httpOnly: true, maxAge: maxAge * 1800 });
return res.status(201).json({ user: user.id });
}
newErrors.password = "inncorect password!";
return res.status(422).json(newErrors);
}
newErrors.email = "inncorect login id!";
return res.status(422).json(newErrors);
});
};
#さいごに
今回はexpress-validatorの使い方とログイン画面で開発者として気を付けることを学びました。セキュリティに関しては先輩社員からタスクとしてどのような配慮をしなくてはいけないのか調査を依頼されて学んだことなので私も最近になってこれらの大切さに気づきました。
おそらくフロントだけ学んでいたら気づかなかったかもしれない内容なのでやはりExpressを勉強しててよかったなーと。引き続きNode.jsについて見聞を深めていきたいです。
#参考URL