Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

codehacker's avatar

"@csrf" and still "419 page exired"

I am currently learning "30 days to learl Laravel" and I am at video 16. I have added @csrf to my form. Although it generates a new token in the source code every time I refresh the tab, it still shows "419 | Page Expired" when I click on "Send". I have already compared my code with the one on github: JeffreyWay/30-days-to-learn-laravel/tree/day-16

I am using Linux, local test environment, and have also tried it under different browsers. To narrow down the error, WHERE would I have to look, HOW does the browser or server compare that it is the RIGHT token? If the browser generates a new token, how does the server know that this token is the correct one? If it is the server that generates a new token, why can I see it in the source code of the user's browser?

Does anyone have any tips for me?

Side note: If possible, I wouldn't want to be handed the solution on a silver platter, but would like to learn something in the process, and even learn HOW something works.

Thank you :-)

Kind regards

0 likes
15 replies
jlrdw's avatar

It compares when you submit the form. Is it working at that point? Why would you refresh the page?

1 like
codehacker's avatar

I reload the page to check if the token is indeed regenerated each time.

I don't know what the token sent in the "hidden" field is compared with. And where I can check where and how it compares. I only know about this "hidden" field.

JussiMannisto's avatar

Start by showing the form code.

The @csrf directive should be enough. The token is checked in a middleware named either VerifyCsrfToken or ValidateCsrfToken, depending on your Laravel version. That middleware is part of the 'web' middleware stack by default. It looks for the token in a parameter named _token and in request headers. The _token field is created by the @csrf directive while the header is used in JS requests.

1 like
codehacker's avatar
<form method="POST" action="/jobs">
    @csrf
    <div class="space-y-12">
        <div class="border-b border-gray-900/10 pb-12">
        <h2 class="text-base font-semibold leading-7 text-gray-900">Create a New Job</h2>
        <p class="mt-1 text-sm leading-6 text-gray-600">We just need a handful of details from you.</p>

        <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
            <div class="sm:col-span-4">
            <label for="title" class="block text-sm font-medium leading-6 text-gray-900">Title</label>
            <div class="mt-2">
                <div class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 sm:max-w-md">
                <input type="text" name="title" id="title" class="block flex-1 border-0 bg-transparent py-1.5 px-3 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" placeholder="Programmierer">
                </div>
            </div>
            </div>

            <div class="sm:col-span-4">
            <label for="salary" class="block text-sm font-medium leading-6 text-gray-900">Salary</label>
            <div class="mt-2">
                <div class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 sm:max-w-md">
                <input type="text" name="salary" id="salary" class="block flex-1 border-0 bg-transparent py-1.5 px-3 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6" placeholder="120000 CHF">
                </div>
            </div>
            </div>
        </div>
        </div>
    </div>
    <div class="mt-6 flex items-center justify-end gap-x-6">
        <button type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button>
        <button type="submit" class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Save</button>
    </div>
</form>
JussiMannisto's avatar

That looks right to me.

One possible explanation is that the CSRF token is not stored on the server because your session is not working. How are you running the app, and what session driver are you using?

If you're using the file driver for sessions, the web server user needs write permissions to the storage/ directory and everything in it.

1 like
jlrdw's avatar

I don't know what the token sent in the "hidden" field is compared with.

The submitted token is compared to one stored in session.

But like @jussimannisto said, make sure session is working.

1 like
codehacker's avatar

In the folder /var/www/LearnLaravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware I found both files ValidateCsrfToken.php and VerifyCsrfToken.php ValidateCsrfToken.php seems to be an Alias because is extends VerifyCsrfToken.php class without any changes.

In the file "VerifyCsrfToken.php" in the function "tokensMatch" I found out the following: "is_string($request->session()->token())" is true. "is_string($token)" is true. "hash_equals($request->session()->token(), $token)" is false. There are 2 different tokens. The "$request->session()->token()" is another token as that one shown in the views source code.

I have installed Apache, PHP, Laravel locally on my computer. Session driver is "database".

The folder /var/www/LearnLaravel/storage/framework/sessions is empty except for the file ".gitignore".

I am a little unsure, which user, which user group, which directory permissions, which file permissions, are appropriate for the "storage" folder.

JussiMannisto's avatar

@codehacker Storage permissions are not the issue if you're using database sessions.

When setting up the project, did you create the session table? You could test if sessions are working by adding these two routes:

Route::get('/write-session', function() {
	session()->put('something', 'foobar');
	return redirect('/read-session');
});

Route::get('/read-session', function() {
	dump(session()->get('something'));
});

If you open the /write-session url in browser and see the text 'foobar' after redirection, sessions are working correctly. If you see null or get an exception, something's wrong with your session setup.

1 like
Snapey's avatar

can you login to the site or have you not got that far? If csrf is not working its almost always either you forgot @csrf or sessions are not working, for which there can be many reasons.

1 like
codehacker's avatar

It shows null. I never got so far. And the sessions table already exists.

jlrdw's avatar

@codehacker I suggest go back and re-read the documentation on setting up database sessions.

1 like
Snapey's avatar

@codehacker open your browser developer tools

in the application tab, can you see cookies from your application?

1 like
JussiMannisto's avatar

Have you modified anything session-related in the config? Meaning the config/session.php file or any of the SESSION_* environment variables like SESSION_DOMAIN, SESSION_CONNECTION etc.

1 like
codehacker's avatar

Yes, I see 2 cookies, one learnlaravel_session cookie and one XSRF-TOKEN cookie, but they are different. And for the session cookie the column HttpOnly is set to true, for the XSRF cookie it is set to false. When I refresh the tab, it refreshes both cookies in the same second, but it creates 2 different cookies.

I haven't modiefied anything in the config/session.php file.

codehacker's avatar
codehacker
OP
Best Answer
Level 1

Not only the "database.sqlite" file needs to be in the group "www-data" but also the folder in which this file is. The folder "database" too has to be in the group "www-data". It solved the problem for me.

I found the solution on the following page: matthewsetter.com/sqlite-attempt-to-write-to-readonly-database/

Thank you for your helps!

Please or to participate in this conversation.