Updated June 2026. Tested on Laravel 13 and PHP 8.4.

CSRF stands for cross site request forgery. It is an attack where another site tricks a logged in user's browser into sending a request to your app, carrying their session, to do something they did not intend. Laravel protects against it for you, and this post covers the three things you actually need to know: the token in forms, sending the token with AJAX, and excluding the routes that should not be checked.

How it works

Laravel generates a CSRF token for each user session. Any request that changes state (POST, PUT, PATCH, DELETE) must carry that token, and the VerifyCsrfToken middleware, which runs on the web routes, checks it. If the token is missing or wrong, the request is rejected with a 419 error. A forged request from another site cannot know the token, so it fails.

Forms: add @csrf

For any HTML form that is not a GET, drop the @csrf Blade directive inside it. That outputs a hidden _token field with the current token.

<form method="POST" action="/profile">
    @csrf

    <input type="text" name="name">
    <button type="submit">Save</button>
</form>

That is the whole job for forms. You do not verify anything yourself; the middleware does it.

AJAX: send the token in a header

For requests made with JavaScript, put the token in a meta tag in your layout's <head>.

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

Then read it and send it as an X-CSRF-TOKEN header. The middleware checks that header as well as the form field. With the Fetch API it looks like this.

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

fetch('/profile', {
    method: 'POST',
    headers: {
        'X-CSRF-TOKEN': token,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name: 'Jane' }),
});

If you use Axios, it is even easier, because Axios reads the XSRF-TOKEN cookie that Laravel sets and sends it back automatically. There is a short guide on the Vue and Axios setup if that is your stack.

Excluding routes

Sometimes a route must skip CSRF, the classic case being a webhook. A Stripe webhook is a server to server POST from Stripe, which has no session and no token, so the check would always fail. Exclude those routes.

In current Laravel, you do this in bootstrap/app.php.

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'stripe/*',
        'webhooks/*',
    ]);
})

Each entry is a URI pattern, and * is a wildcard. Only exclude routes that genuinely cannot carry a token, like webhooks. Never exclude a normal form route to make an error go away.

A note on API routes

Routes in routes/api.php are stateless and not protected by this middleware at all. They are not session based, so CSRF does not apply. Secure those with API tokens instead, using Laravel Sanctum.

That is CSRF in Laravel: @csrf in forms, the X-CSRF-TOKEN header for AJAX, and a short exclude list for webhooks. The middleware handles the rest. Questions welcome in the comments.