React+ReduxアプリにNode.js(express)とJWTで認証・認可周りの処理を実装する①API側

## はじめに

React+ReduxアプリケーションにNode.js(express)でAPIを作り、jsonwebtoken(JWT)で認証・認可周りの処理を実装してみました。今回はAPI側の実装の内容を書いていきます。

## 使ったもの
* Node(v12.15.0)
* express(v4.17.1)
* jsonwebtoken(v8.5.1)
* body-parser(v1.19.1)
* nodemon(v2.0.3)
* REST Client(VSCodeの拡張機能)

## ディレクトリ構成

```
root/
　├ api/
　│  ├ login.js
　│  └ users.js
　├ config/
　│  └ jwt.config.js
　├ middlewares/
　│  └ verifyToken.js
　├ app.js
　└ request.rest
```

## 作るもの

### [POST] /api/v1/login
ユーザーIDとパスワードを付与してリクエストすれば、認証が通ればjwtでトークンを返す

### [GET] /api/v1/users
トークンをヘッダーに付与してリクエストすれば、ユーザー情報を返す

## プロジェクト作成
プロジェクト作成して、必要なパッケージをインストールします。

```
npm init -y
npm install --save express jsonwebtoken body-parser nodemon
```

## app.jsを作成
とりあえずexpressを読み込んで、appで初期化して、app.listenで起動する普通の流れです。
ルーティングは別ファイルにするので、express.Routerを初期化してリターンします。

```app.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.use("/api/v1",(() => {
    const router = express.Router();
    router.use("/login", require("./api/login.js"));
    router.use("/users", require("./api/users.js"));
    return router;
  })()
);

app.listen(5000, function () {
  console.log("Example app listening on port 5000!");
});

```

## login.jsを作成

認証は本来はデータベースから行うが今回は割愛します。

```login.js
const router = require("express").Router();
const jwt = require("jsonwebtoken");
const config = require("../config/jwt.config");

// POST /api/v1/login
router.post("/", (req, res) => {
  // 本来はデータベースからユーザーIDとパスワードを認証するが割愛
  if (
    (req.body.userId == "001" && req.body.passWord == "qwerty") ||
    (req.body.userId == "002" && req.body.passWord == "asdfgh")
  ) {
    const payload = {
      userId: req.body.userId,
    };
    
    const token = jwt.sign(payload, config.jwt.secret, config.jwt.options);

    res.json({
      isSuccess: true,
      token: token,
    });
  } else {
    res.json({
      isSuccess: false,
      message: "ユーザーIDまたはパスワードが違います。",
    });
  }
});
module.exports = router;
```

jwt.config.jsに設定を記載する。

```jwt.config.js
module.exports = {
  jwt: {
    secret:
      "cdfa54a89e0fbd4596213bf175336cfe05a78a73383f6dab39b8d374e7dceba53630546ff4c998b7bab9e53eaab2519a48e970b094315cf7472b811e45c1ea29",
    options: {
      algorithm: "HS256",
      expiresIn: "20m",
    },
  },
};
```

シークレットキーはコマンドプロンプトで以下入力で取得したものです。

```
node
require("crypto").randomBytes(64).toString("hex")
```

## ログインリクエストを試してみる

nodemonをインストールしてあれば以下で起動できます。

```javascript
nodemon app.js
```

今回リクエストにはVSCodeの拡張機能の[REST Client]を使います。request.restファイルに以下のように記述すれば、
上部に[Send Request]が表示されるのでクリックすれば、リクエストできます。

```javascript:request.rest
POST http://localhost:5000/api/v1/login
Content-Type: application/json
{
  "userId": "001",
  "passWord": "qwerty"
}
```

認証に成功すれば以下レスポンスがあると思います。これでトークンが取得できましたで、ユーザー情報を取得します。

```javascript
{
  "isSuccess": true,
  "token": "xxxxxxxxxxxxxxxxxxxx"
}
```
## verifyToken.jsを作成

まずトークンを認証するミドルウェアを作成します。
リクエストヘッダーには以下のようにトークンを付与します。

```
Authorization: Bearer xxxxxxxxxxxxxxxxxxxx
```

ヘッダーからトークンを取得して認証処理を行います。
認証が成功したら、req.decodedにpayloadで設定した値が格納される。

```verifyToken.js
const jwt = require("jsonwebtoken");
const config = require("../config/jwt.config");

function verifyToken(req, res, next) {
  // splitで半角スペースで分割して後ろ側がTokenになる
  const authHeader = req.headers.authorization;
  const token = authHeader && authHeader.split(" ")[1];

  if (token) {
    jwt.verify(token, config.jwt.secret, function (error, decoded) {
      if (error) {
        return res.status(403).send({
          isSuccess: false,
          message: "トークンの認証に失敗しました。",
        });
      } else {
        req.decoded = decoded;
        next();
      }
    });
  } else {
    return res.status(401).send({
      isSuccess: false,
      message: "トークンがありません。",
    });
  }
}
module.exports = verifyToken;

```

## users.jsを作成

こちらもは本来はデータベースから情報を取得するが今回は割愛します。
先ほど作成した[verifyToken]を付与すれば、認証が行われ[req.decoded]にユーザーIDが格納されるので、その値をもとにユーザー情報を取得してレスポンスを返します。

```login.js
const router = require("express").Router();
const verifyToken = require("../middlewares/verifyToken");

// GET /api/v1/users
router.get("/", verifyToken, (req, res) => {
  // 本来はデータベースからユーザーIDを元にデータを取得するが割愛
  let results = {};
  if (req.decoded.userId == "001") {
    results = {
      userId: req.decoded.userId,
      name: "Tom",
    };
  } else if (req.decoded.userId == "002") {
    results = {
      userId: req.decoded.userId,
      name: "Mike",
    };
  }

  res.json(results);
});
module.exports = router;
```

## ユーザー情報取得リクエストを試してみる
先ほどログインした時のトークンをリクエストヘッダーに付与して、リクエストする。

```javascript:request.rest
GET http://localhost:5000/api/v1/users
Authorization: Bearer xxxxxxxxxxxxxxxxxxxx
```

認証に成功すれば以下のようにレスポンスがあるはずです。

```javascript
{
  "userId": "00001",
  "name": "Tom"
}
```

## まとめ

これで、API側の実装は完了です。本来はデータベースが必須になるのでそのうちMySqlかMongoDBあたりで実装するかもしれません。
とりあえず[次回](https://qiita.com/rpf-nob/items/d5c19299470ef7e8704d)はフロント側の実装をしていきたいと思います。何かまずいところあればコメントお願いします。

## 参考にしたもの

[Introduction to JSON Web Tokens](https://jwt.io/introduction/)
[JWT Authentication Tutorial - Node.js](https://www.youtube.com/watch?v=mbsmsi7l3r4&t=801s)