りゅうじの学習blog

学習したことをアウトプットしていきます。

2022年4月18日 Node.js express csrf対策

csurfのパッケージを使用してcsrf対策をします。

注: パッケージ名はcsurf となる点に注目してください。csrf というパッケージも存在します。

csurf

http://expressjs.com/en/resources/middleware/csurf.html

csrf

https://github.com/pillarjs/csrf

実装

前提

  • テンプレートはejsを使用します。
  • Expressのバージョン14.6.0以降を使用します。(body-parserは標準搭載されているのでインストールは不要)
  • 今回はcsurfの実装をメインなのでexpress等、前段階で必要なパッケージのインストールは割愛します。

csurfはクッキーもしくはセッションを利用します。

今回はセッションで行う方法をアウトプットします。

まず必要なパッケージをインストールしましょう。

yarn add csurf
yarn add express-session

ログイン機能への実装例です。ログインはRouterを使ってモジュール化しています。(express-sessionのミドルウェアは本体のapp.jsに実装しているためここでは記述はありませんが割愛します)

login.js

const router = require("express").Router();
const db = require("../models");
const bcrypt = require("bcrypt");
const csrf = require("csurf");
const csrfProtection = csrf();

router.get("/", csrfProtection, (req, res) => {
  res.render("login", { csrfToken: req.csrfToken() });
});

router.post("/", csrfProtection, async (req, res) => {
  const email = req.body.email;
  const authentication = await db.User.findOne({ where: { email: email } });
  const normalPassword = req.body.password;
  const hashedPassword = authentication?.password;
  bcrypt.compare(normalPassword, hashedPassword, (error, passwordEqual) => {
    if (authentication && passwordEqual) {
      req.session.userId = authentication.id;
      req.session.userName = authentication.name;
      if (req.session.url) {
        res.redirect(req.session.url);
      } else {
        res.redirect("/index");
      }
    } else {
      res.redirect("/login");
    }
  });
});

module.exports = router;
  • const csrf = require('csurf'); でインストールしています。
  • const csrfProtection = csrf() でCSRF対策のためのミドルウェアを作ります。今回はクッキーではなくセッションにトークンを保持してCSRF対策をしています。引数を渡さないとデフォルトでセッションを設定した事になるのでここでは引数はなしです。(クッキーを使用する場合は引数で設定します)
  • csrfProtection をCSRF対策を施したい各ルートに対して埋め込みます。
  • トークンは req.csrfToken() で取得できるので、これをテンプレートにパラメータとして渡してフォームに埋め込むためにres.render の第二引数に持たせています。

login.ejs

<!DOCTYPE html>
<html lang="jp">
<head>
  <%- include("./_share/meta.ejs") %> 
  <title>Document</title>
</head>
<body>
  <h1>ログイン画面</h1>
    <form action="/login" method="post">
    <input type="hidden" name="_csrf" value="<%= csrfToken %>" />
    <p>メールアドレス</p>
    <input type="text" name="email">
    <p>パスワード</p>
    <input type="password" name="password">
    <input type="submit" value="ログイン">
    <a href="/signup">ユーザー登録画面へ</a>
    <a href="/index">一覧ページへ</a>
    <a href="/passwordReset">パスワードをお忘れの方はこちら</a>
</body>
</html>

formタグの一つ下のinputタグに注目してください。

  • type=”hidden” とすることで見えないフォームを生成しています。
  • value=”<%= csrfToken %>” ここで先程、res.render の第二引数で持たせたトークンをフォームに埋め込んでいます。

こちらを検証ツールで確認してみます。

トークンの値を変更するとpostリクエストはエラーになり、実装したフォームからでしかpostリクエストは通らないよう実装でき、csrf対策ができている事を確認できるかと思います。

参考

csurf