Express の CSRF攻撃対策 - Express

ファイルの編集

以下のパッケージをインストールします。

// connect-flash
npm install connect-flash

// express-session
npm install express-session

Express-generator で作成されたアプリのファイルを以下のように編集します。

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const session = require('express-session');  // 追加
const flash = require('connect-flash'); // 追加
const crypto = require("crypto"); // 追加

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// 追加
function generateCsrfToken(){
  return crypto.randomBytes(32).toString('hex');
}

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 追加
app.use(session({
  secret: 'secret_key',
  resave: false,
  saveUninitialized: false
}));

// 追加
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = generateCsrfToken();
  }
  next();
});

app.use(flash()); // 追加

... 省略 ...

routes/index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  const csrfToken = req.session.csrfToken;
  res.render('index', { msg: "", csrfToken: csrfToken });
});

router.post('/', function(req, res, next) {
  const receivedToken = req.body.csrfToken;
  // エラー処理を確認する場合、トークンを一致しないように上記をコメントアウトし、以下のコメントアウトを解除します
  // const receivedToken = "エラー";
  const sessionToken = req.session.csrfToken;

  console.log(receivedToken);
  console.log(sessionToken);

  if (receivedToken === sessionToken){
    console.log('フォームが正常に送信されました!');
    const csrfToken = req.session.csrfToken;
    return res.render("index", { msg: 'トークンは一致しました', csrfToken: csrfToken });
  } else {
    console.log('フォームの送信に失敗しました。');
    req.flash( 'msg', 'トークンが一致しません' );
    return res.redirect("/");
  }
  
});

module.exports = router;

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>formの送信</h1>
    <p>
      <% if(msg != '') { %>
        <%= msg %>
      <% } %>
    </p>
    <form action="/" method="POST">
      <input type="hidden" name="csrfToken" value="<%= csrfToken %>">
      <input type="text">
      <input type="submit"></button>
    </form>
  </body>
</html>