【Laravel】フォーム送信時に419 Page Expiredが発生する件について調べてみた

#概要
本記事は、[PHPフレームワークLaravel入門 第2版](https://www.amazon.co.jp/PHP%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AFLaravel%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798060992/ref=pd_lpo_1?pd_rd_i=4798060992&psc=1)で学習している中の疑問・つまづきの備忘録です。

今回はCSRF対策の際のエラー解決と根本原因についてまとめます。

#発生したエラー

フォーム送信時に以下のエラーが発生しました。

![スクリーンショット 2022-01-08 12.03.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475547/014f3ed8-896f-6f72-be5e-602b6f2d2eb9.png)

##ソースコード
```index.blade.php
<form action="/hello" method="post">
    <table>

        ///////中略

        <tr>
            <th>Message: </th>
            <td>
                <input type="text" name="msg" value="{{ old('msg') }}">
            </td>
        </tr>
    </table>
</form>

```


```VerifyCsrfToken.php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        // 
    ];
}

```


##原因
以下の2点が原因です。

- index.blade.phpのformタグ内に<span>＠</span>csrfディレクティブを記載していない



- にも関わらず、VerifyCsrfTokenクラスの配列$exceptにCSRF対策から除外するアクション名を記載していない


##解決策1(CSRF対策を行いたい場合)

index.blade.phpのformタグ内に<span>＠</span>csrfディレクティブを追記

```index.blade.php
<form action="/hello" method="post">
    <table>
        @csrf //これを追記する

        ///////中略

        <tr>
            <th>Message: </th>
            <td>
                <input type="text" name="msg" value="{{ old('msg') }}">
            </td>
        </tr>
    </table>
</form>

```

##解決策2(CSRF対策を行わない・必要ない場合)

VerifyCsrfTokenクラスの配列$exceptにCSRF対策から除外するアクション名を追記

※この場合formタグに<span>＠</span>csrfディレクティブの追記は不要

```VerifyCsrfToken.php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        'hello', //helloアクションではCSRF対策を行わない指定
    ];
}
```

##なぜ上記の対応を行わないと419 Page Expiredが発生するのか

こうすればエラーが解消できる！というのは分かりましたが、なぜこのような対応が必要なのでしょうか。
裏側で何が起こっているのか調べてみました。

答えは[Laravel 8.x CSRF保護](https://readouble.com/laravel/8.x/ja/csrf.html)に記載がありました。
>アプリケーションで"POST"、"PUT"、"PATCH"、"DELETE" HTMLフォームを定義するときはいつでも、CSRF保護ミドルウェアがリクエストを検証できるように、フォームに非表示のCSRF_tokenフィールドを含める必要があります。便利なように、@csrf Bladeディレクティブを使用して、非表示のトークン入力フィールドを生成できます。

Laravelを使用しないPHPでの開発の際に必要だったtype="hidden"でvalueにトークンを持つinputタグを自動で生成してくれています。

```php
<form method="POST" action="/profile">
    @csrf
    <!-- Equivalent to... -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" /> //生成されるタグのトークン入力フィールド
</form>

```

さらに、

>webミドルウェアグループへデフォルトで含まれているApp\Http\Middleware\VerificationCsrfTokenミドルウェアは、リクエスト入力のトークンがセッションに保存されたトークンと一致するかを自動的に検証します。この２トークンが一致すれば、認証済みユーザーがリクエストを開始したことがわかります。



つまり今回は

- index.blade.phpのフォームに<span>＠</span>csrfディレクティブが記載されておらず、トークンを持つinputタグが生成されていなかった
- VerificationCsrfTokenクラスの配列$exceptでCSRF対策を除外したい当該のフォーム送信時のアクション名が追加されていなかった
- VerificationCsrfTokenで入力トークン検証が失敗した

という理由でフォーム送信時に419 Page Expiredが発生したようです。

#まとめ
CSRF対策する場合は、formタグに<span>＠</span>csrfディレクティブを記載する。
しない場合はVerificationCsrfTokenクラスの配列$exceptにCSRF対策から除外するアクション名を追記する。




色々と調べてみると新たな発見があって楽しいですね!


#参考文献
>[PHPフレームワークLaravel入門 第2版](https://www.amazon.co.jp/PHP%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AFLaravel%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798060992/ref=pd_lpo_1?pd_rd_i=4798060992&psc=1)
>[Laravel 8.x CSRF保護](https://readouble.com/laravel/8.x/ja/csrf.html)