tech.chakapoko.com
Home / Node.js / Express

[Node.js][Express]csurfモジュールでCSRF対策を行う

ExpressでCSRF対策を行うための csurf モジュールの使い方を紹介します。

インストール

csurf モジュールはnpmからインストールできます。

$ npm install --save csurf

csurf モジュールはクッキーもしくはセッションを利用します。クッキーを利用するときは cookie-parser モジュールを、セッションを利用するときは express-session などのセッションモジュールを利用します。今回はセッションを利用します。

セッションを利用できるようにするため、express-session モジュールをインストールします。またHTMLフォームにトークンを埋め込むため、テンプレートエンジンとして pug を、POSTリクエストのボディをパースするために body-parser モジュールを利用します。

$ npm install --save express-session pug body-parser

サンプルコード

次は csurf モジュールを使ってCSRF対策を施した例です。

app.js

const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const csrf = require('csurf');
const app = express();

const sess = {
  secret: 'secretsecretsecret',
  cookie: { maxAge: 60000 },
  resave: false,
  saveUninitialized: true,
}

if (app.get('env') === 'production') {
  app.set('trust proxy', 1)
  sess.cookie.secure = true
}

const csrfProtection = csrf({ cookie: false });

app.use(bodyParser.urlencoded({ extended: false }));
app.use(session(sess));

app.set('view engine', 'pug');

app.get('/', csrfProtection, (req, res) => {
  res.render('index', { csrfToken: req.csrfToken() });
});

app.post('/', csrfProtection, (req, res) => {
  res.send(req.body.text);
});

app.listen('3000', () => {
  console.log('Application started');
});

まず const csrf = require('csurf'); でモジュールをインストールしたのち、 const csrfProtection = csrf() でCSRF対策のためのミドルウェアを作ります。今回はクッキーではなくセッションにトークンを保持してCSRF対策を行うので cookie オプションをfalseに設定しました。

あとは csrfProtection をCSRF対策を施したい各ルートに対して埋め込みます。トークンは req.csrfToken() で取得できるので、これをテンプレートにパラメータとして渡してフォームに埋め込みます。`

トークンは _csrf という名前で埋め込みます。

views/index.pug

html
  body
    form(action="/" method="POST")
      input(type="hidden" name="_csrf" value=csrfToken)
      input(type="text" name="text" value="")
      input(type="submit" name="submit")

動作確認

http://localhost:3000 にアクセスし、フォームのHTMLソースを表示すると次のようにトークンが埋め込まれていることを確認できます。

<form action="/" method="POST">
  <input type="hidden" name="_csrf" value="xe1t9is6-Q1bcuuJ8G5rdTXWCRqzkSat7FUI">
  <input type="text" name="text" value="">
  <input type="submit" name="submit">
</form>

このフォームをポストすると csrfProtection フォームによりトークンのチェックが行われます。試しにブラウザの開発者ツールなどでトークンを変更したりするとリクエストがエラーになることが確認できます。

Ajaxの場合

Ajaxリクエストの場合はフォームではなく、HTMLの <head> タグ内の <meta> タグとしてトークンを埋め込むのが簡単です。

<meta name="csrf-token" content="xe1t9is6-Q1bcuuJ8G5rdTXWCRqzkSat7FUI">

リクエストする時は次のように CSRF-Token ヘッダにトークンを仕込みます。

var token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

fetch('/process', {
  credentials: 'same-origin',
  headers: {
    'CSRF-Token': token
  },
  method: 'POST',
  body: {
    favoriteColor: 'blue'
  }
})