ホーム>Laravel>Laravel 419 Error Troubleshooting Quick Reference
Laravel

Laravel 419 Error Troubleshooting Quick Reference

Thank you for your continued support.
This article contains advertisements that help fund our operations.

A quick reference for troubleshooting Laravel's 419 error, with the key things to check.

関連動画

Related Video

This is a video where we actually tried out the content from the article! If anything is unclear in the article, please check out the video.

The video provides further explanations and demonstrations, so it should be helpful.

Subscribe to Our Channel

If you found this video helpful, please consider subscribing to our channel or giving it a thumbs up! It really motivates us to create more content.

Questions and Feedback

If you have any questions or feedback regarding this article or the video, feel free to leave them in the comment section of the video. Your input is greatly appreciated and will help us improve our content in the future!

Laravel 419 Error Troubleshooting Quick Reference

If you see 419 PAGE EXPIRED, start by checking how you're sending the request.

Check out the sections below for more details!

Symptom or situationWhat to checkFix or troubleshooting approach
Sending a POST request from a Blade formWhether the form being submitted contains @csrf and sends _tokenAdd @csrf inside the form, reopen the page, and submit again
Sending a POST request with Ajax, fetch, jQuery, or Axios within LaravelThe meta tag and X-CSRF-TOKEN headerAdd the token from the meta tag to the header
Using Sanctum + Axios in an SPACSRF initialization, the XSRF-TOKEN cookie, and the X-XSRF-TOKEN headerFetch /sanctum/csrf-cookie before logging in or performing similar actions
Hosting the frontend and API on separate subdomainsCORS and Axios settings for sending cookiesCheck supports_credentials, withCredentials, and withXSRFToken
Hosting the API on a completely different domainWhether you're confusing this with a subdomain setupUse API token
Investigating the relationship between CSRF and sessionsVerifyCsrfToken and StartSessionSee the experiment below, and don't treat disabling protection as a fix
Getting a 419 when submitting a form even with @csrf addedWhether it's outside the form, or missing from a search formPlacing it inside the form being submitted is a suggested approach
Getting a 419 on a page left idle for a long time or when using multiple tabsSession expiration and logins or logouts in other tabsPossible steps include reloading the page, logging in again, and checking SESSION_LIFETIME
Still getting a 419 after changing settingsWhether cached configuration or other cached data remainsconfig:clear and cache:clear are possible steps
Cookies aren't saved after adding or editing configuration filesWhether a PHP configuration file starts with a blank lineThere has been a reported case resolved by moving <?php to the first line
The session doesn't persist even though a token is presentWhether StartSession is applied, cookie attributes, and Sanctum settingsPossible steps include checking route middleware and whether cookies are saved and sent
Getting a 419 with a load balancer or multiple serversWhether sessions are shared between serversA shared store such as Redis is a possible approach
Changing the message shown when a 419 occursWhere exceptions are handled and the Laravel versionCatching TokenMismatchException is one suggested approach
CSRF validation fails for a webhook from an external serviceWhether the same protection used for browser forms is appropriateConsider excluding specific routes and verifying the sender's signature

Solutions

1. I fixed Blade forms by adding @csrf inside the form

When I submitted a form with POST without @csrf, I got this error:

419 PAGE EXPIRED

Adding @csrf inside the form being submitted fixed it.

<form method="POST" action="{{ route('post') }}">
  @csrf
  <input name="post" />
  <button type="submit">post</button>
</form>

@csrf generates a hidden input tag like this:

<input
  type="hidden"
  name="_token"
  value="vond93ovKGBBXpALpxAu4Ka9V646MW8tm9BvLRFp"
/>

To check this, use your browser's developer tools to confirm:

  1. The HTML contains name="_token"
  2. The submitted data includes _token in the Network tab

2. I fixed Ajax requests within Laravel by adding the meta tag's token to the header

For asynchronous requests that don't use a form, I handled this by adding the token embedded in a meta tag to the X-CSRF-TOKEN header.

First, I added this meta tag inside the <head> of the page rendered by Blade.

(All the JavaScript, jQuery, and Vue examples below need this code.)

<head>
  <!-- Other tags -->
  <meta name="csrf-token" content="{{ csrf_token() }}" />
</head>

With JavaScript (fetch)

Use document.querySelector to get the token from the meta tag and add it to the fetch headers.

function post() {
  const url = "/post"
  fetch(url, {
    method: "POST",
    headers: {
      "X-CSRF-TOKEN": document
        .querySelector('meta[name="csrf-token"]')
        .getAttribute("content"),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      // Data to send
    }),
  })
    .then(res => {})
    .catch(err => console.log(err))
}

This code works on its own, even on pages that don't load jQuery.

With jQuery (ajax)

With jQuery's $.ajax, adding the token from the same meta tag does the job too.

let url = "/post"
$.ajax({
  headers: {
    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
  },
  url: url,
  type: "POST",
  data: {
    // Data to send
  },
})
  .done(function () {})
  .fail(function (data) {})

With Axios in Vue and similar setups

For Axios, I configured the headers in one place in resources/js/bootstrap.js so I wouldn't have to write them for every request.

window.axios = require("axios")
window.axios.defaults.headers.common = {
  "X-Requested-With": "XMLHttpRequest",
  "X-CSRF-TOKEN": document
    .querySelector('meta[name="csrf-token"]')
    .getAttribute("content"),
}

Whichever approach you use, the thing to check is whether X-CSRF-TOKEN in the outgoing request matches the value in the meta tag.

3. I fixed Sanctum + Axios requests with the cookie approach

If you're using Sanctum + Axios to make requests from a JavaScript framework outside the Laravel project, use this code:

axios.get("/sanctum/csrf-cookie").then(response => {
  // Login logic…
})

Place it in the entry-point file where the app is initialized, such as resources/js/app.js or main.js, or before createApp().mount() if you're using Vue.

Running separate projects on subdomains

Allowing requests with cookies in config/cors.php does the job.

'supports_credentials' => true,

In resources/js/bootstrap.js, add these Axios settings:

axios.defaults.withCredentials = true
axios.defaults.withXSRFToken = true

Here's the Japanese documentation for Laravel 11.x Sanctum.

4. For completely different domains, use API tokens

The approach above worked for my subdomain setup, but when I used a completely different domain for the API server, I couldn't set cookies that way.

So I'll use an API token implementation.

Create an API that sends a token from the backend.

$token = $user->createToken('frontend')->plainTextToken;
return response()->json([
    'token' => $token,
]);

Store the token received from the API in localStorage or a similar location.

Then add the token to the headers when sending requests with axios, like this:

await axios.get("https://backend-api.com/api/user", {
  headers: {
    Authorization: `Bearer ${token}`,
    Accept: "application/json",
  },
})

Security settings, such as where to store the token and how long to keep it, are tricky with this setup.

Unless you have a specific reason, using the same domain or subdomains is easier.

Common Mistakes

@csrf is outside the form or missing from a search form

When checking, make sure @csrf is inside the form you're actually submitting, not just somewhere on the page.

Session expiration or state changes across multiple tabs

When investigating, record the lifetime value in config/session.php, SESSION_LIFETIME, how long the page was left idle, and the order of logins and logouts in other tabs.

Apparently, possible troubleshooting steps include saving a copy of what you've entered and reopening the page, or logging in again before submitting if needed.

Configuration changes aren't taking effect or cached data remains

Sometimes edits to files such as config/cors.php don't take effect, so try running:

php artisan cache:clear
php artisan config:clear

Sessions aren't shared in a load-balanced environment

If you have multiple backend servers with a load balancer distributing requests, you need a way to share sessions between the servers.

I use Redis in these situations because it makes session management easier.

To use Redis for sessions in Laravel, adjust these .env settings:

SESSION_DRIVER=redis
SESSION_CONNECTION=default
SESSION_LIFETIME=120

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0

Laravel's official docs also seem to recommend choosing a shared store such as Redis or a database when using multiple web servers (official session documentation).

I haven't tried this myself.

How It Works

Middleware defines the relationship between CSRF protection and sessions.

In the setup I was using at the time, CSRF protection was configured in the web middleware group applied to routes/web.php.

As an experiment, I commented out those two settings like this:

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,
    ],
];

I also removed @csrf from the form.

<form method="POST" action="{{ route('post') }}">
  <input name="post" />
  <button type="submit">post</button>
</form>

Submitting a POST request in this state doesn't produce a 419 error.

That means removing VerifyCsrfToken disabled CSRF protection.

I also found that removing the StartSession middleware stopped CSRF tokens from being generated.

I don't recommend removing these middleware just to get rid of a 419 error.

These notes come from testing a setup for Laravel 10 or earlier, and apparently the configuration lives somewhere else in Laravel 11 and later, but I haven't verified that yet.

Please Provide Feedback
We would appreciate your feedback on this article. Feel free to leave a comment on any relevant YouTube video or reach out through the contact form. Thank you!