Laravel 419 Error - VerifyCsrfToken issue

Chad picture Chad · Oct 11, 2018 · Viewed 12.4k times · Source

I have multiple Laravel sites hosted on the same server. With the latest site I've created, the contact form refuses to submit without throwing a 419 error. I have set up the routing in my web.php file just like the other websites, which have live, working contact forms, and I'm generating and sending the token exactly the same way - with {{ csrf_field() }}.

I found an answer to a similar question stating that you can disable Csrf checking by adding entries to the $except array in app/Http/Middleware/VerifyCsrfToken.php. I have verified that this does indeed resolve the 419 error:

protected $except = [
    'contact',
    'contact*',
];

But of course I wish to keep the Csrf functionality, and I only updated the $except array for troubleshooting value.

Does anyone know what may be different about the new Laravel environment that would have this 419 behavior despite passing the generated token? I have tried updating a number of ENV settings and toggling different things, but nothing other than modifying the $except array has had any influence on the issue.

Update

Since there has been a bit of discussion so far, I figured I'd provide some additional info and code.

First, this is an ajax form, but don't jump out of your seat just yet. I have been testing the form both with and without ajax. If I want to test with ajax, I just click the button that's hooked up to the jQuery listener. If not, I change or remove the button's ID, or run $("#formName").submit(); in the console window.

The above (ajax, old-fashioned submit, and the jquery selector with .submit();) all result in the exact same response - a 419 error.

And for the sake of completeness, here's my ajax code which is working on all of the other websites I'm hosting. I define a postData array to keep it all tidy, and I added a console.log() statement directly after it to (again) confirm that token is generated just fine and is being passed correctly with the request.

var postData = {
            
    name: $("#name").val(),
    email: $("#email").val(),
    message: $("#message").val(),

    _token: $("input[name=_token]").val()

};

console.log(postData);


$.post("/contact", postData, function (data) {

...

Any ideas? Could there be a configuration issue with my ENV or another file?

Progress Update!

Because the other sites are working just fine, I cloned an old site and simply overwrote the files that I changed for the new website, and bam! It's working now. Doing a little bit more digging, I ran php artisan --version on the cloned version of the site versus the non-working version, and here are the results:

Working Version: Laravel Framework 5.7.3

Non-working Version: Laravel Framework 5.7.9

Perhaps this is a bug with Laravel? Or perhaps some packages on my server are out of date and need to be updated to work with the new version of Laravel?

Answer

agm1984 picture agm1984 · Jun 10, 2020

TLDR: This post contains lots of potential issues and fixes; it is intended for those scouring for related bonus information when stuck.

I just encountered this error using Laravel Sanctum in what looks like improperly setup middleware. Sanctum uses the auth:sanctum middleware for the guard, which is some kind of extension of the auth guard of which Laravel uses as the default, but session is handled by the web middleware group.

I can't exactly verbalize some of this internal-Laravel stuff; I am more experienced with JavaScript than PHP at the moment.

In my api.php file, I had the login/register/logout routes, and in my Kernel.php file, I copied \Illuminate\Session\Middleware\StartSession::class, from the web group into the api group.

I had to do that to fix my login unit test that was throwing an error about "Session store not on request". Copying that allowed me my postJson request to work in the unit test, but sometime later, I started seeing 419 CSRF error posting from the JavaScript app (which is bad because it worked fine earlier).

I started chasing some filesystem permission red-herring in the /storage/framework/sessions folder, but the issue wasn't that (for me).

I later figured out that with Laravel Sanctum and the default AuthenticatesUsers trait, you must use the web guard for auth, and the auth:sanctum middleware for protected routes. I was trying to use the api guard for auth routes and that was central to my 419 errors with the AuthenticatesUsers trait.

If anyone gets 419 while CSRF was working or should work, I recommend doing some \Log::debug() investigations at all the key points in your system where you need these to work:

Auth::check()

Auth::user()

Auth::logout()

If you get strange behaviour with those, based on my observations, there is something wrong with your config related to sessions or something wrong with your config related to web, api guards.

The guards have bearing on the AuthManager guard which maintains state over multiple requests and over multiple unit tests.

This is the best description I found, which took over a week for me to discover:

Method Illuminate\Auth\RequestGuard::logout does not exist Laravel Passport

As a random final example, if your session is somehow generating the CSRF token using data from the web middleware group while your routes are set to use api, they may interpret the received CSRF incorrectly.

Besides that, open Chrome dev tools and goto the Applications tab, and look at the cookies. Make sure you have the XSRF-TOKEN cookie as unsecure (ie: not httpOnly).

That will allow you to have an Axios request interceptor such as this:

import Cookies from 'js-cookie';

axios.interceptors.request.use(async (request) => {
    try {
        const csrf = Cookies.get('XSRF-TOKEN');
        request.withCredentials = true;

        if (csrf) {
            request.headers.common['XSRF-TOKEN'] = csrf;
        }

        return request;
    } catch (err) {
        throw new Error(`axios# Problem with request during pre-flight phase: ${err}.`);
    }
});

That is how my current Laravel/Vue SPA is working successfully.

In the past, I also used this technique here:

app.blade.php (root layout file, document head)

<meta name="csrf-token" content="{{ csrf_token() }}">

bootstrap.js (or anywhere)

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

const token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

In my opinion, most problems will stem from an incorrect value in one or more of these files:

  • ./.env

  • ./config/auth.php

  • ./config/session.php

Pay close attention to stuff like SESSION_DOMAIN, SESSION_LIFETIME, and SESSION_DRIVER, and like I said, filesystem permissions.

Check your nginx access.log and/or error.log file; they might contain a hint.