Solution for 419 Page Expired error in Laravel

The 419 Page Expired error is very common and easy to fix in Laravel applications. It’s caused by the internal framework mechanism called CSRF protection. CSRF stands for Cross-Site Request Forgery and is one of the most popular attacks.

An error page layout may differ between the framework versions, but the error code (419) and the error message (Page Expired) are the same. The following screenshot comes from Laravel 8.

To avoid this issue, every POST, PUT, PATCH, and DELETE request have to have a csrf token as a parameter. Depending on the way you send your request, you have several options to append this parameter.

Solution #1 – Blade directive

When you create a form in a Blade template, the solution is extremely simple. Blade template engine has a built-in directive (@csrf) that generates a hidden HTML input containing the token. The directive should be added just after opening <form> tag.

<form method="POST" action="/register">
    @csrf
        
    <label for="email">Email</label>
    <input type="email" name="email">

    <label for="email">Password</label>
    <input type="password" name="password">

    <button type="submit">Save</button>
</form>

Alternatively, you can create a token input manually, using csrf_token() method. Outcome will be identical.

<!-- Equivalent for @csrf directive -->
<input type="hidden" name="_token" value="{{ csrf_token() }}">

Solution #2 – Header of the Ajax request

As for Ajax request, the solution is a little different, but also quite simple. All you have to do is to add the csrf token to the head section of your HTML document and send it as an X-CSRF-TOKEN header with your request.

<head>
    <meta name="csrf-token" content="{{ csrf_token() }}" />
</head>
var request;
var form = $("form");
var data = {
    'email': form.find('input[name="email"]').val(),
    'password': form.find('input[name="password"]').val()
};
var headers = {
    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}

request = $.ajax({
    url: "/register",
    type: "post",
    headers: headers,
    data: data
});

request.done(function (){
    console.log("It works!");
});

Solution #3 – Disabling CSRF validatation for some endpoints

For some specific endpoints, you can disable CSRF validation. Specific URLs can be excluded in the $except array of the VerifyCsrfToken class. This way you can exclude either the exact URL or group of URLs with a common prefix.

// /app/Http/Middleware/VerifyCsrfToken.php

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        'payment/*',  // exclude all URLs wit payment/ prefix
        'user/add' // exclude exact URL
    ];
}

Excluding from CSRF protection should be used only for endpoints that are used by external applications (like payment providers). However, it’s also convenient to use api route file when you have many endpoints like that. They would be automatically excluded from the CSRF protection.

Conclusion

CSRF protection is by default enabled for all POST, PUT, PATCH, and DELETE requests within web routes file (those in api file are excluded). That approach has many advantages and allows developers to focus on more complex issues. However, that may be also confusing for less experienced programmers because requires more knowledge about a request lifecycle. Anyway, the three solutions I presented in this post are more than enough to handle all possible use cases and easily work with CSRF tokens in Laravel applications.

You May Also Like

Leave a Reply

Your email address will not be published. Required fields are marked *