CakePHP 3 のテンプレートで FormHelper を使わずに、直接 CSRF トークンを埋め込む

はじめに

CakePHP 3 には FormHelper という仕組みがあります。これを用いると Form 関連のタグの記述量が減ったりして便利なのですが、タグが見えづらくなるデメリットもあります。

これを使わずに、テンプレートに直接 <form> などを記述すると、POST送信した際に「CSRF token mismatch.」のエラーが出ます。

だからといって CSRF 保護をやめてしまうわけにもいきません。

そこで今日は FormHelper を使用せずに、直接 CSRF トークンを埋め込む方法をご紹介します。

今回使用した CakePHP のバージョンは 3.8.4 です。

目次
  1. 実装方法
  2. Javscript で自動挿入
  3. おわりに

1. 実装方法

実装はとても簡単で、<form> 内に下記1行を追記するだけでOKです。

src/Template/sample.ctp
<form method="post">
  <!-- 省略 -->
  <input type="hidden" name="_csrfToken" value="<?= $this->request->getParam('_csrfToken') ?>">
</form>

2. Javascript で自動挿入

上記の方法だと、毎回 <input> を記述する必要があり、ちょっと手間に感じるかもしれません。そんな時は Javascript で下記のようなコードを用意すれば、自動的に挿入することもできます。

src/Template/Layout/default.ctp
<script>
  // CSRFトークン
  var CSRF_TOKEN = "<?= $this->request->getParam('_csrfToken') ?>";

  window.onload = function() {
    // 各 <form> で method="post" なら CSRFトークンの <input> を末尾に追加
    var forms = document.getElementsByTagName("form");
    var length = forms.length;
    for (var i = 0; i < forms.length; i++) {
      var form = forms[i];
      if (form.getAttribute("method") !== "post") {
          continue;
      }
      // CSRFトークンの <input> を生成
      var inputCsrf = document.createElement("input");
      inputCsrf.setAttribute("type", "hidden");
      inputCsrf.setAttribute("name", "_csrfToken");
      inputCsrf.value = CSRF_TOKEN;
      // <form> に追加
      form.appendChild(inputCsrf);
    }
  };
</script>

3. おわりに

CakePHP 3 の FormHelper を使うと色々と自動でやってくれるので便利ではあるものの、その部分がブラックボックスになり、把握するためのコストが生じます。

<form> などを直接テンプレートに記述する場合は CSRF 保護を無効化せずに、上記のような手段でトークンを追加するのがセキュアでいいと思います。