【Node.js】ExpressとFirebase Admin SDKとJWTを使ってログイン機能を実装する

#はじめに
メインはフロントですが、趣味でExpressを触っている者です。以前Passport.jsを使って認証機能を実装したのですが今回はtokenを使ってログイン機能を実装していきたいと思います。個人開発ということもあるのでDBには慣れているFirebaseからFirestoreを使用しています。

###解説①(authMiddleware.js)
ログインユーザーでなければアプリを使えないという仕様になっています。
requestAuthとcheckUserという関数でtokenを確認（verify）して問題がなければnuxtし次のmiddlewareに進みます。もしtokenが違ければloginページへリダイレクトするようになっています。

```javascript:authMiddleware.js
const jwt = require("jsonwebtoken");
const admin = require("../plugins/firebase");
const db = admin.firestore();

const requestAuth = (req, res, next) => {
  const token = req.cookies.jwt;
  // check json web token exist & is verfied?
  if (token) {
    jwt.verify(token, "secret", (err, decodedToken) => {
      if (err) {
        console.log(err.message);
        res.redirect("/login");
      } else {
        console.log(decodedToken);
        next();
      }
    });
  } else {
    res.redirect("/login");
  }
};

const checkUser = (req, res, next) => {
  const token = req.cookies.jwt;
  console.log(token);
  // check json web token exist & is verfied?
  if (token) {
    jwt.verify(token, "secret", (err, decodedToken) => {
      if (err) {
        console.log(err.message);
        res.locals.user = null;
        next();
      } else {
        db.collection("users")
          .where(admin.firestore.FieldPath.documentId(), "==", decodedToken.id)
          .get()
          .then((docSnapshot) => {
            return (user = docSnapshot.docs[0].data());
          })
          .then((user) => {
            res.locals.user = user;
            next();
          });
      }
    });
  } else {
    res.locals.user = null;
    res.redirect("/login");
  }
};

module.exports = { requestAuth, checkUser };
```
###解説②（authController.js）
ログインボタンを押下するとlogin_postの処理が走ります。Firestoreのusersコレクションからログインフォームに入力したemailを照合し、あっていたらスナップショットを撮ってきます。その後passwordの照合も行い問題がなければtokenを生成しuserIDを返します。

```javascript:authController.js
const login_post = (req, res) => {
  const { email, password } = req.body;
  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((user) => {
      console.log(user);
      const newErrors = {};
      if (user) {
        const auth = bcrypt.compare(password, user.hashPassword);
        if (auth) {
          const token = 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);
    });
};
```
###解説③(index.js)
いつものindex.jsです。ここでは"/"でrequestAuth、それ以外のパス("*")でcheckUserというAuthMiddlewareで作成した関数を挟み込ませています。これで「ログインしっぱなし」という状態を作っています。

```javascript:index.js
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const authRoutes = require("./routes/authRoutes");
const cookieParser = require("cookie-parser");
const { requestAuth, checkUser } = require("./middleware/authMiddleware");

app.set("view engine", "ejs");

app.use(bodyParser.json());
app.use(express.json());
app.use(cookieParser());
app.use(express.static("public"));
app.use(express.urlencoded({ extended: true }));
app.use(authRoutes);

var server = app.listen(3000, function () {
  console.log("Node.js is listening to PORT:" + server.address().port);
});

app.get("*", checkUser);

app.get("/", requestAuth, (req, res) => {
  res.redirect("/lists");
});
```

#さいごに
Passport.jsと比べるとJWTの方が実装しやすい感触でした。tokenはCookieに入れるかLocalStorageに入れるか論争なども見受けたのでもう少し調査をしようと思います（そもそもtokenの認証・認可の認識も怪しい…）。フロントではサーバーから来たtokenをheaderに乗せているコードを実際に見たり、axiosラッパーを作ってheaderにtokenを乗せる実装をしたことがあったので今回の記事を通し`クライアント → サーバー`の知見だけでなく、`サーバー → クライアント`の仕組みを知ることができたのでよかったです。

#参考URL

https://creators-note.chatwork.com/entry/2018/09/25/132218

https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/

https://www.macnica.co.jp/business/security/manufacturers/okta/blog_20210901.html