Laravelバックエンドにfetch APIでDELETEリクエストを送ろうとしてハマった話

この記事は ATJ-TECH Advent Calendarの16日目です。

adventar.org

先日はレトロな駄文を失礼しました。今回はWebアプリケーション開発でハマったポイントのお話です。

実行環境

  • OS: Ubuntu 20.04 Server
  • Laravel: 8.26.1
  • PHP 8.0.12

ハマったポイント

  1. 削除ボタンがクリックされたら確認ダイアログを表示させ、OKであればDELETEメソッドでリクエストを送信する
  2. Laravel側のコントローラに定義してあるレコードの削除処理が走る
    以上のような処理を実現するため、1.の部分をfetch APIを利用して次のように実装しました。
<button onclick="confirmAndDelete('/api/hoge')">
    DELETE
</button>

function confirmAndDelete(target) {
    let conf = confirm("Delete this record?");
    if (conf) {
        fetch(target, {
            method: 'DELETE'
        });
    }
}

 これをブラウザに表示させ削除ボタンをクリックしてみましたが、登録されているレコードは削除されずに表示されたままになってしまいました。

原因解明

 Laravel側で受け取ったリクエストを全部吐き出すようなログを作成して確認してみたところ、DELETEメソッドでのリクエスト自体は受け取られていました。
 次に、ブラウザの開発者ツールからfetch APIの動作を確認してみたところ、419エラーではじかれていたことが判明しました。
 普段はform要素内に@csrfディレクティブを記述することでCSRFトークンを埋め込んでいたのですが、Javascriptを利用してDELETEリクエストを送信する場合でもトークンを持たせる必要があることまでは理解していなかったことが原因でした。

問題解決

 リクエストボディにフォームデータ_tokenとしてCSRFトークンを持たせてあげれば解決しそうではあったんですが、DELETEメソッドにはリクエストボディを持たせられない模様。
 CSRFトークンのチェックを無効化することも考えたのですが、さすがに安全性を犠牲にするのは気が向かなかったので公式ドキュメントを確認したところ、以下のような記述を見つけました。

POSTパラメータとしてCSRFトークンをチェックすることに加えて、App\Http\Middleware\VerifyCsrfTokenミドルウェアはX-CSRF-TOKENリクエストヘッダもチェックします。
参照:CSRF保護 8.x Laravel

 以上のことを参考にして、リクエストの送信前にX-CSRF-TOKENヘッダを追加するようにしたところ、無事登録されたレコードを削除することができました。
 以下変更後のコード。

<button onclick="confirmAndDelete('/api/hoge', '{{ csrf_token() }}')">
    DELETE
</button>

function confirmAndDelete(target, token) {
    let conf = confirm("Delete this record?");
    if (conf) {
+       let header = new Headers();
+       header.append("X-CSRF-TOKEN", token);
        fetch(target, {
            method: 'DELETE',
+           headers: header
        });
    }
}