Laravelで419エラーが発生してPOSTできないときの解決方法
いつもご利用ありがとうございます。
この記事には広告が掲載されており、その広告費によって運営しています。
目次
関連動画
質問やフィードバック
この記事や動画に関する質問やフィードバックあれば、動画のコメント 欄にてお気軽にコメントしてください。
今回は、PHP のフレームワークである Laravel で POST したときに、「419 PAGE EXPIRED」のエラーが発生したときの対処法について書いていきます。
エラー文
419 PAGE EXPIRED
原因の多くは @csrf の書き忘れである
<form method="POST" action="{{ route('post') }}">
//↓を書く!!!
@csrf
//~~~~色々な<input />タグがここに入る
</form>
大体がこれを忘れていることが原因になってきます。
上記の@csrf
で、以下のタグが生成されます。
<input type="hidden" name="_token" value="vond93ovKGBBXpALpxAu4Ka9V646MW8tm9BvLRFp">
これがないと、419 エラーになります。
その他、Ajax 等での解決方法は記事の後半にあるのでスクロールしてご確認ください。
419エラーはどんな時に起こる?
Laravel では、最初からセキュリティ対策のためのシステムがいくつも設定されています。
通常の PHP では、とくに何もしないで POST メソッドを送信することが可能ですが、Laravel ではそれができません。
逆に、何の対策もせずに POST してしまうのが非常にセキュリティ的によくないため、Laravel の初期に使う設定では、対策なしでは POST できないようになっています。
今回やることは、主に CSRF(クロスサイトリクエストフォージェリ)という攻撃の対策のひとつの、トークンを使った対策となります。
419 エラーの原因は、このトークンの設置がうまくいってなかったり、
トークンとセッションを照合するのが上手く動いていないことが原因で起きています。
CSRF トークンとは?
CSRF トークンとは、正しいウェブ サイトからの POST 送信であるかどうかを、トークン(文字列)によって判断するものになります。
生 PHP では、ログイン時にトークンを生成システムを自作し、セッションに添付するシステムを自作しないとダメなようです。
そして、そのセッションのトークンをサーバー側で照合して、正しくなければ通さないといったシステムになります。
生 PHP では大体
<input type="hidden" name="token" value="ここに生成されたトークンが入る" />
こんな感じで直書きし、自前でコーディングする必要があります。
Ajax などの非同期通信の場合(Laravel プロジェクト内)
まず、meta タグを追加する(全てに共通)
いいね機能など、form ではなく、jQuery などでサクッと送信するような機能を生成する時には、このように form を作成しないと思います。
その時は手順が少し違います。
まず、
<head>
</head>
タグの中に追記します。
<head>
<!-- ほかに色々タグがあるはずです -->
<meta name="csrf-token" content="{{ csrf_token() }}" />
</head>
JavaScript のとき
function post() {
const url = '/post';
fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
//送信するやつ
})
})
.then(res => {})
.catch(err => console.log(err));
このように書きます。
headers のところに CSRF-TOKEN を載せます。
これは、meta タグに埋め込まれている CSRF トークンを付与しているので、最初の head の中に記述する必要があります。
jQuery のとき
let url = '/post';
$.ajax({
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
url: url,
type: 'POST',
data: {
}
})
.done(function() {
})
.fail(function(data) {});
}
こんな感じになります。
Vue などで使う、axios のとき、
毎回 token を付与するのが大変なので、post するときには csrf トークンを取得する記述を書きます。
js/bootstrap.js
window.axios = require("axios")
//csrfでaxiosする
window.axios.defaults.headers.common = {
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
}
このような設定をする必要があります。bottstrap.js でする理由としては、毎回記載するのがめんどくさいので一括でやってしまおうということです。
Laravel プロジェクト外の JS フレームワークの場合
Sanctum かつ Axios の場合
CSRF トークンをサーバーから受けとり、「XSRF-TOKEN」というクッキーで保存します。
その後 POST する際、「X-XSRF-TOKEN」というヘッダーでサーバーへ渡すことによって CSRF 保護が可能になり、419 エラーは発生しなくなります。
このリクエスト中に、Laravel は現在の CSRF トークンを含む XSRF-TOKEN クッキーをセットします。このトークンは、後続のリクエストへ X-XSRF-TOKEN ヘッダで渡す 必要があります。これは、Axios や Angular HttpClient などの一部の HTTP クライアントライブラリでは自動的に行います。JavaScript HTTP ライブラリで値が設定されていない場合は、このルートで設定された XSRF-TOKEN クッキーの値と一致するように X-XSRF-TOKEN ヘッダを手作業で設定する必要があります。
引用:https://readouble.com/laravel/11.x/ja/sanctum.html#CSRF 保護
「Axios」や「Angular HttpClient」では、自動的に行ってくれますので、実質的にやることは少ないです。
CSRF の初期化をするため、GET で以下の URL(初期設定だと)にリクエストします。リクエストタイミングは、サービスによって任意のタイミングとなるので検討する必要があります。
axios.get("/sanctum/csrf-cookie").then(response => {
// ログイン処理…
})
別プロジェクト(サブドメイン)の場合、実質的に CORS の設定を変更する必要があります。
config/cors.php ファイル内の設定を変えます。
supports_credentials: true
resources/js/bootstrap.js の設定を変えます。
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
完全に別ドメインの場合、これではできない
サブドメインの場合は、以上のような実装方法で可能ですが、完全に別ドメインを API サーバーとして使う場合は注意が必要です。
サブドメインとは、ドメインの前に任意の文字列.を付けて別ドメインとして運用することができるものです。
メイン
laratech.jp
サブドメイン
example.laratech.jp
Axios は、サブドメインの場合、withCredencials を True にすればクッキーをセットしてくれますが、完全に別ドメインの場合、この方法ではセットしてくれなかったので他の方法を探す必要があると思います。
※ 当時読んだ、根拠となる記事が見つかりませんでした。
※ 他の方法や最新で変更情報あればお問い合わせ(Google フォーム、メアド入力不要で一方的に送れます)からお知らせください。
補足
この CSRF トークンをどこでシステム化されているかというと Middleware で定義されています。
定義された Middleware を通常使うであろう web.php で使用するというような設定になっていました。
※ Laravel11 から、以下の記述は無くなっており、bootstrap/app.php にて追加するようになっています。
app/Http/Kernel.php(Laravel10 以前)
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
// \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
この部分に VerifyCsrfToken::class という Middleware を使うというように書かれています。
ではこれを外してみます。
<form method="POST" action="{{route('post')}}">
<input name="post" />
<button type="submit">post</button>
</form>
さきほどの@csrf を取りました。
これで POST すると、419エラーが発生しなくなりました。
つまり、このミドルウェアを付けないと CSRF 保護が動かないということです。
ちなみに csrf トークンは、StartSession の Middleware を外すと生成されなくなるので、これも必要です。外すことはないと思いますが。
Middleware の中身までは中々追う機会がないので、追えたら記事にでもしてみたいものです。
まとめ
蛇足が多々ありましたが、419 エラーの解決方法と、なぜこのようなエラーが発生するのかという、Laravel を触ったことがある人にとっては 100%通るところの記事を書いてみました。
このエラーがあるおかげで、トークン無しで POST を実装することもないので逆に安心ですね。
また、フレームワークが最初から付けてくれている機能は、生言語を触る時には
「絶対に付けなければならない機能」
という解釈もできて、生の言語に対する知識もつくので、便利 × 勉強の両立をさせてくれるのでフレームワーク大好きです。
記事に対する意見、修正等はお問い合わせまたは Youtube コメントで受け付けておりますのでよろしくお願いします。